Next.js Redirect Tanpa Flashing Content

Written on May 18, 2021 by

photo of me

Theodorus Clarence.

––– views • 4 min read

Read in English

Introduction

Next.js melakukan prerender untuk static page, kemudian baru melakukan hydrate ke interaktivitas yang full secara client side.

Dengan konsep tersebut, maka kita akan melihat halaman HTML dan CSS terlebih dahulu, kemudian beberapa detik kemudian, kita mendapatkan JavaScript, dan semua interaktivitas seperti click button.

The Problem

Pada Create React App, melakukan redirect atau history.push bukan sebuah masalah, karena semua data difetch pada client-side, sehingga kita akan mendapatkan file lengkap dengan Javascriptnya. Flashing content otomatis tidak menjadi masalah.

Tetapi, di Next.js kita mendapatkan static page, kemudian setelah hydration, code JavaScript untuk redirect baru akan berjalan. Ini menjadi masalah karena akan ada content yang muncul untuk beberapa detik sebelum aplikasi di redirect. Hal ini tentunya tidak diinginkan terutama untuk konten yang tidak boleh dilihat oleh unauthorized user.

Saya melihat problem ini masih ada beberapa di production app, beberapa memang menutupi data" penting karena biasanya di fetch secara client-side, tetapi kadang shell dari kontennya masih muncul untuk waktu sebentar. Coba buka website ini app.splitbee.io/projects. Kamu seharusnya tidak memiliki akses ke website ini, tetapi karena problem tersebut, kita bisa melihat dashboard shell muncul sebentar, baru diarahkan ke /login page

Demo

Sebenarnya ada cukup banyak jawaban di internet untuk menggunakan method seperti server-side rendering, dan memanfaatkan cookies dengan menggunakan dangerouslySetInnerHTML di Head tag.

Method yang saya gunakan ini tidak perlu hal" tersebut, tetapi kita wajib menggunakan full-page loader untuk menutupi kontennya.

Solution

Saya membuat demo di https://learn-auth-redirect-nextjs.vercel.app/

Anda bisa mencoba membuka page tersebut, dan langsung mengunjungi learn-auth-redirect-nextjs.vercel.app/blocked di new tab. Kamu akan secara singkat melihat loader, kemudian diredirect ke '/' tanpa melihat konten di dalamnya sama sekali.

Ada 2 approach yang saya temukan.

1. Melakukan checking di setiap 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>
);
}
jsx

Disini, kita mendapatkan nilai isAuthenticated dari auth context, kamu bisa melihat repositori untuk melihat code lebih lengkap.

Dari code diatas, akan mereturn FullPageLoader componet terlebih dahulu sambil menunggu page render, dan menunggu auth context selesai mengecek token ke API menggunakan useEffect.

Potongan code ini menggunakan useEffect di authentication context, untuk memverifikasi token yang biasanya disimpan di localStorage. Jika kamu ingin mengetahu Authentication Context pattern lebih lanjut, saya memiliki code snippet tentang itu.

Context juga mengembalikan isLoading value, dan kita menampilkan loader ketika sedang loading, sampai kita mendapat value isAuthenticatednya

Pattern ini akan secara efektif memblock content yang tidak ingin kita tampilkan ke unauthorized users. Tetapi, dengan menggunakan first approach ini, akan menyulitkan jika kita harus menambahkan pattern useEffect pada setiap pagenya. Maka saya membuat component PrivateRoute, mirip seperti React Router patternnya CRA.

2. Menggunakan 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;
}
jsx

Dengan menggunakan komponen ini, kita bisa menyantumkan route yang mau kita protect di _app.js

//_app.js
import 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;
jsx

I'm open for all suggestions and contributions to improve 🚀. Open a PR on the repository or email me at theodorusclarence@gmail.com

Demo

1. Tanpa menggunakan full page loader & not authenticated /blocked-unhandled

Demo

Dapat kita lihat, tulisan text merah masih bisa kita lihat.

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

Demo

Dengan menggunakan full page loader, maka tidak ada konten yang akan ke flash

3. Menggunakan full page loader & authenticated dengan cara mengecek token localStorage

Demo

Menggunakan full page loader akan tetap berjalan ketika user sudah memiliki token di localStorage

Enjoying my post? Kindly subscribe to my newsletter if you want an update everytime I post.