skip to Main Content

I am making a simpe "email confirm" page.
It gets confirm key from URL, sends a POST request to API which returns true or nothing.
If key is correct the API removes it from database and returns true so the page can show success message:

// pages/confirm/[key].vue

<script setup>
    const route = useRoute();
    const key = route.params.key;

    const confirmed = ref(false);

    try
    {
        const result = await $fetch('/api/confirm', {
            method: 'post',
            body: { key: key },
        });

        confirmed.value = !!result;
    } catch {}
</script>

<template>
        <div v-if="confirmed">Email confirmed!</div>
</template>

The problem is when I open the page in browser with correct key passed in URL I only see "Email confirmed" message for a few milliseconds and then it disappears with "hydration mismatch" warning in console.

As far as I understand, Nuxt renders the page on server where the page has "Email confirmed" message. But after API call information about confrim keys gets deleted from database so when it comes to browser render it calls for API once again, this time it gets an empty response (because there is no such email confirm key anymore) and removes successs message.

How can I fix it?
Maybe there is a way to make only one API call or somehow save its result?
I am new to Nuxt and I really don’t know how to propely work with this "double rendering" thing…

2

Answers


  1. The simplest way is specifing the pages/confirm/[key].vue as a CSR-page-component in nuxt.config.ts

    export default defineNuxtConfig({
        routeRules: {
            'confirm-key': {
                ssr: false
            }
        }
    });
    

    since the page is only rendered on client side, the $fetch will only execute in users’ browsers


    The second way is to use useLazyFetch instead of $fetch, also, you need to wrap the <div> in the <client-only>

    // 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>
    

    However, in my view, verifying the key on the client side may not be a good idea, since it may cause safety issues.

    you may do this on the server side which I mean the nuxt server (not the /api server which may powered by PHP/Java/Go/etc)

    // 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');
    }
    
    Login or Signup to reply.
  2. Change $fetch function to useFetch composable – it is merely a wrapper around, but it automatically dedupes server/client API calls. Not only it will get you rid of the hydration mismatch, but it also improves your app performance.

    There was a nice example with an explanation by Daniel Kelly of VueSchool about this exact topic at this year’s NuxtNation conference – it starts at 3:35:00

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search