The Problem:
In a Nuxt 3 application, a confirmation page for email verification is implemented. The page sends a POST request to an API to validate the confirmation key and displays a success message upon successful validation. However, the confirmation message disappears after a few milliseconds with a ‘hydration mismatch’ warning in the console. This is because the page is initially rendered on the server with the confirmation message, but when the page is rendered again in the browser, a second API call is made and the validation fails due to the key being removed from the database. The challenge is to resolve the hydration mismatch issue and maintain the confirmation message after the initial render.
The Solutions:
Solution 1: Specify the page as a CSR-page-component
In nuxt.config.ts
, specify the pages/confirm/[key].vue
as a CSR-page-component by adding the following code:
export default defineNuxtConfig({
routeRules: {
'confirm-key': {
ssr: false
}
}
});
By doing this, the page will only be rendered on the client side, eliminating the hydration mismatch issue. The $fetch
call will only execute in users’ browsers, ensuring that the confirmation message is displayed correctly.
Solution 2: Use `useLazyFetch` and ``
In the pages/confirm/[key].vue
file, use the useLazyFetch
composable instead of $fetch
. Additionally, wrap the <div>
containing the confirmation message in the <client-only>
component. The code should look like this:
// pages/confirm/[key].vue
<script setup>
const route = useRoute();
const key = route.params.key;
const confirmed = ref(false);
try {
const result = await useLazyFetch('/api/confirm', {
method: 'post',
body: { key: key },
});
confirmed.value = !!result;
} catch {}
</script>
<template>
<client-only>
<div v-if="confirmed">Email confirmed!</div>
</client-only>
</template>
The useLazyFetch
composable ensures that the API call is only made on the client side, preventing the hydration mismatch issue. The <client-only>
component ensures that the confirmation message is only rendered on the client side.
Solution 3: Verify the key on the server side
You can also verify the confirmation key on the server side using the useNuxtApp
and callWithNuxt
composables. This approach is more secure as it prevents potential safety issues associated with verifying the key on the client side. The code should look like this:
// pages/confirm/[key].ts
import { useNuxtApp, callWithNuxt } from '#app';
definePageMeta({
middleware: [
async (to: RouteLocationNormalized) => {
const nuxtApp = useNuxtApp();
const { data } = await useFetch(
'/api/confirm',
{
method: 'POST',
body: { key: to.params.key },
}
);
await callWithNuxt(
nuxtApp,
() => {
nuxtApp.$verifyResult = data.value;
}
);
}
]
});
const nuxtApp = useNuxtApp();
const route = useRoute();
const verifyResult: Ref<boolean> = ref(nuxtApp.$verifyResult ?? false);
if (verifyResult.value) {
navigateTo('/confirm/success.vue');
} else {
navigateTo('/confirm/failed.vue');
}
In this approach, the API call to verify the key is made on the server side, ensuring that the confirmation message is displayed correctly without any hydration mismatch issues.
Solution 2: Use `useFetch` Composables
Instead of using the $fetch
function, use the useFetch
composable. It automatically dedupes server/client API calls, eliminating hydration mismatch and improving app performance. In your example, this change would resolve the issue of the “Email confirmed” message disappearing after a few milliseconds.
Example:
pages/confirm/[key].vue
<script setup>
const route = useRoute();
const key = route.params.key;
const confirmed = ref(false);
const { data: result } = useFetch('/api/confirm', {
method: 'post',
body: { key: key },
});
confirmed.value = !!result?.value;
</script>
<template>
<div v-if="confirmed">Email confirmed!</div>
</template>
Explanation:
- The
useFetch
composable is imported. - Within
useFetch
, the API call to'/api/confirm'
is made with the appropriate options, including the HTTP method and request body. - The composable returns a response object, which is destructured to obtain the
data
property. - The
data
property is used to set theconfirmed
ref value, taking into account the existence and truthiness of the result.
This revised solution eliminates the hydration mismatch issue by automatically deduping the API call on the client-side, ensuring that the "Email confirmed" message persists after the initial render.
Q&A
How to fix hydration mismatch when confirming an email in Nuxt 3?
Specify the page as a CSR-page-component in nuxt.config.ts
or use useLazyFetch
with <client-only>
.
Why using useFetch
instead of $fetch
is recommended?
useFetch
automatically dedupes server and client API calls, improving performance and eliminating hydration mismatches.
Video Explanation:
The following video, titled "Error Handling in Nuxt 3 - YouTube", provides additional insights and in-depth exploration related to the topics discussed in this post.
Error Handling in Nuxt 3 00:00 Introduction to Error Handling 00:46 createError 02:28 NuxtErrorBoundary 03:46 Client Side Error Handling ...
The following video, titled "Error Handling in Nuxt 3 - YouTube", provides additional insights and in-depth exploration related to the topics discussed in this post.
Error Handling in Nuxt 3 00:00 Introduction to Error Handling 00:46 createError 02:28 NuxtErrorBoundary 03:46 Client Side Error Handling ...