Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 18, 2021 03:25 pm GMT

Next.js Redirect Without Flashing Content

Next.js prerenders the static page, then hydrate the site to full interactivity client-side

That means, we see our page first which is the HTML and the CSS, and a split second later, we get the JavaScript and all of the interactivity like button clicks.

The Problem

In Create React App redirecting or doing history.push is not really a problem because all of the data that was sent is fetch on the client-side, including the static page (HTML & CSS). So there won't be any flashing content, and the app will smoothly redirect or push the page.

But, in Next.js, we get the static page first, then only after finishing the hydration, the javascript code that does the redirecting will run. This becomes a problem when we are making a page with an authentication barrier because the unauthorized user can see the content briefly before getting redirected.

I saw a lot of this problem even in the production app, maybe they still cover up the data because some of it was fetched client-side, but the shell sometimes still shows up. Try opening this website https://app.splitbee.io/projects/theodorusclarence.com. You are not supposed to have access to this page. You will see a flash of the dashboard shell then only after that flash, you will get redirected to the login page.

Splitbee

There are a lot of answers lying around the internet to use methods such as server-side rendering the page, and utilizing cookies by using dangerouslySetInnerHTML in the Head.

This method of blocking the page does not need any of that, but we will need to have a full-page loader to block the content.

Solution

I created a demo on https://learn-auth-redirect-nextjs.vercel.app/

You can try opening up the page, and directly go to learn-auth-redirect-nextjs.vercel.app/blocked. You will briefly see the loader, then get redirected to the / route.

There are 2 approach that I found.

1. Checking on every single page

import { useEffect } from 'react';import { useRouter } from 'next/router';import { useAuth } from '@/contexts/auth';import FullPageLoader from '@/components/FullPageLoader';export default function blocked() {    const router = useRouter();    const { isAuthenticated, user, logout, isLoading } = useAuth();    useEffect(() => {        if (!isLoading && !isAuthenticated) {            router.push('/');        }    }, [isAuthenticated, isLoading]);    if (isLoading || !isAuthenticated) {        return <FullPageLoader />;    }    return (        <div className='py-12 space-y-4 layout'>            <h1>YOUR CONTENT THAT SHOULD NOT BE SEEN UNLESS AUTHENTICATED</h1>        </div>    );}

Here, we are getting the isAuthenticated from the Auth Context, you can see the repository for more details.

This set of code will return the FullPageLoader component first while waiting for the page rendered and getting hydrated, then after that, the useEffect will do the checking if we are authenticated.

This code is using useEffect in the Authentication Context, to verify the token that is usually stored in localStorage. If you want to see this Authentication Context pattern, I have a code snippet for that.

The context are returning isLoading Value, and we show the loader when it is loading, until we get the value of isAuthenticated.

This pattern will effectively block the content that we don't want to give to unauthorized users. But using the first approach, it will be painful to add that pattern to every authenticated page we have. So I try to create a PrivateRoute, kind of similar to the CRA's React Router pattern.

2. Using PrivateRoute Component

import { useEffect } from 'react';import { useRouter } from 'next/router';import { useAuth } from '@/contexts/auth';import FullPageLoader from './FullPageLoader';export default function PrivateRoute({ protectedRoutes, children }) {    const router = useRouter();    const { isAuthenticated, isLoading } = useAuth();    const pathIsProtected = protectedRoutes.indexOf(router.pathname) !== -1;    useEffect(() => {        if (!isLoading && !isAuthenticated && pathIsProtected) {            // Redirect route, you can point this to /login            router.push('/');        }    }, [isLoading, isAuthenticated, pathIsProtected]);    if ((isLoading || !isAuthenticated) && pathIsProtected) {        return <FullPageLoader />;    }    return children;}

By using this component, we can specify the routes that we want to protect in _app.js

//_app.jsimport SEO from '@/next-seo.config';import '@/styles/globals.css';import { AuthProvider } from '@/contexts/auth';import PrivateRoute from '@/components/PrivateRoute';function MyApp({ Component, pageProps }) {    // Add your protected routes here    const protectedRoutes = ['/blocked-component'];    return (        <>            <AuthProvider>                <PrivateRoute protectedRoutes={protectedRoutes}>                    <Component {...pageProps} />                </PrivateRoute>            </AuthProvider>        </>    );}export default MyApp;

I'm open for all suggestions and contributions to improve . Open a PR on the repository or email me at [email protected]

Demo

  1. Without using full page loader & not authenticated /blocked-unhandled

    Blocked unhandled

    As you can see, the red text is still flashed briefly

  2. Using full page loader & not authenticated /blocked-component

    Blocked without token

    Using full page loader, no content will be flashed

  3. Using full page loader & authenticated by checking the token

    Blocked with Token

    Using full page loader will still work if they have the token in localStorage


Original Link: https://dev.to/theodorusclarence/next-js-redirect-without-flashing-content-5bio

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To