Integrating Termly’s consent management with Nuxt 3 turns out to be harder than just inserting a script tag. Here’s a solution that resolves hydration mismatches and UI glitches, ensuring smooth functionality and user experience.
The Challenge ¶
When building modern web applications, consent management is crucial for compliance with privacy regulations like GDPR. Termly offers a popular solution, but integrating it with a server-side rendered (SSR) framework like Nuxt 3 presents unique challenges.
Initially, adding Termly’s script to PrioMind, our application powered by Nuxt 3, seemed straightforward:
// app.vue
<script setup lang="ts">
useHead({
script: [
{
src: "https://app.termly.io/resource-blocker/[yourId]",
defer: true,
}
]
})
</script>
However, this led to several issues:
- Hydration mismatches, leading to appearing, then disappering of the consent banner
- Nuxt UI notifications rendering incorrectly
Understanding the Problem ¶
The root of these issues lies in the conflict between server-side rendering and client-side scripting. Nuxt 3, being an SSR framework, renders the initial HTML on the server. When Termly’s script runs on the client, it injects content that wasn’t present in the server-rendered HTML, causing hydration mismatches.
Moreover, the script’s positioning and execution timing affected other UI elements, particularly Nuxt UI notifications.
The Solution ¶
The following is a solution that addresses these issues:
// app.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const termlyLoaded = ref(false)
onMounted(() => {
if (document.querySelector('script[src*="app.termly.io/resource-blocker"]')) {
termlyLoaded.value = true
return
}
const script = document.createElement('script')
script.src = 'https://app.termly.io/resource-blocker/[yourId]?autoBlock=on'
script.async = true
script.onload = () => {
termlyLoaded.value = true
}
const firstScript = document.getElementsByTagName('script')[0]
if (firstScript && firstScript.parentNode) {
firstScript.parentNode.insertBefore(script, firstScript)
} else {
document.head.appendChild(script)
}
})
</script>
<template>
<div>
<ClientOnly>
<div v-if="termlyLoaded" id="termly-code-snippet-support"></div>
</ClientOnly>
<NuxtPage />
<UNotifications />
</div>
</template>
Let’s break down the key aspects of this solution:
-
Client-side script loading: It loads the Termly script dynamically in the
onMounted
hook, ensuring it only happens on the client side. -
Early script insertion: It attempts to insert the script as early as possible in the document to address Termly’s positioning warning.
-
Conditional rendering: It uses a
termlyLoaded
ref to conditionally render the Termly support div, preventing it from appearing before the script has loaded. -
Error handling: It includes checks and a fallback method for script insertion to handle various edge cases.
The Result ¶
This solution resolves the hydration mismatches, fixes the notification rendering issues, and ensures Termly loads as early as possible. The consent management banner now coexists peacefully with the rest of our Nuxt 3 application.
Lessons Learned ¶
-
SSR and client-side scripts require careful handling: When integrating third-party scripts in an SSR environment, consider their execution timing and impact on hydration.
-
Conditional rendering is powerful: Using
ClientOnly
and reactive variables to control rendering can prevent many hydration issues. -
Error handling is crucial: Always include fallback options and error checks, especially when dealing with dynamic script insertion.
Conclusion ¶
Integrating third-party scripts like Termly into a Nuxt 3 application can be challenging, but with a thoughtful approach, it’s possible to achieve seamless integration. By understanding the interplay between server-side rendering and client-side scripting, we can develop solutions that maintain the benefits of both while avoiding common pitfalls.
Michael Schmidle
Founder of PrioMind. Start-up consultant, hobby music producer and blogger. Opinionated about technology, strategy, and leadership. In love with Mexico. This blog reflects my personal views.
Nov 15, 2023 · 2min read
Mastering Keyboard Interactions in Shadcn-Vue: A Fix for Complex UIs
In the intricate dance of UI components within Vue applications, a misstep can disrupt the rhythm. Discover how a straightforward tweak can harmonize your keyboard interactions in shadcn-vue in complex nested components. Continue…
Sep 26, 2023 · Nov 11, 2023 · 2min read
The Better Nuxt Directus Authentication Middleware
The Nuxt Directus docs give you a basic auth middleware, but it’s got some quirks. Here’s how to bulletproof it. Continue…
Apr 18, 2020 · 6min read
Accelerate Your Website—With Your Logo
Most people who care about their website’s performance probably use a website logo created by vector graphics software. Here’s how to take advantage of vectorized logos to speed up your website. Seriously. Continue…
Aug 18, 2019 · May 14, 2020 · 6min read
Unhide Virtual Network Adapters in Windows 10
In Windows’ latest releases, Microsoft hides virtual adapters and networks by default. In some cases though, you need them to be available just like regular adapters and networks. Continue…