Router Guards, Lazy Routes, and Suspense
Introduction
Protected routes redirect unauthenticated users to login. Lazy loading splits JavaScript so users download only the code for pages they visit. Suspense shows fallbacks while lazy chunks load. This chapter covers auth wrappers, React.lazy, loaders, and scroll restoration.
Prerequisites
- React Router Basics
- Context API or Zustand for auth state
Protected Route Wrapper
src/components/ProtectedRoute.tsx:
Router:
{
path: "dashboard",
element: (
<ProtectedRoute isAuthenticated={Boolean(token)}>
<DashboardPage />
</ProtectedRoute>
),
},With Zustand later:
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
// ...
}Compare Vue navigation guards.
Login Redirect Back
LoginPage.tsx:
Lazy-Loaded Pages
Or wrap once at layout:
// RootLayout.tsx
<Suspense fallback={<PageSkeleton />}>
<Outlet />
</Suspense>Code explanation:
import()creates a separate chunk indist/assets/Suspensemust wrap lazy components
Nested Admin Layout
{
path: "/",
element: <ProtectedRoute isAuthenticated={isLoggedIn}><AdminLayout /></ProtectedRoute>,
children: [
{ index: true, element: <Navigate to="dashboard" replace /> },
{ path: "dashboard", element: <DashboardPage /> },
{ path: "settings", element: <SettingsPage /> },
],
},AdminLayout.tsx:
Full project: Admin Dashboard With Auth.
Loader (Data Router)
Prefetch data before render:
async function postLoader({ params }: { params: { slug?: string } }) {
const res = await fetch(`/api/v1/posts/${params.slug}`);
if (!res.ok) throw new Response("Not Found", { status: 404 });
return res.json();
}
{
path: "posts/:slug",
element: <PostDetailPage />,
loader: postLoader,
},PostDetailPage.tsx:
import { useLoaderData } from "react-router-dom";
export function PostDetailPage() {
const post = useLoaderData() as Post;
return <h1>{post.title}</h1>;
}Alternative: fetch in usePost(slug) custom hook—Custom Hooks.
Error Boundaries (Route Level)
{
path: "posts/:slug",
element: <PostDetailPage />,
loader: postLoader,
errorElement: <PostErrorPage />,
},Scroll Restoration
import { ScrollRestoration } from "react-router-dom";
// In root layout
<ScrollRestoration />Or manual window.scrollTo(0, 0) in useEffect on pathname change.
Auth Guard Checklist
-
/loginpublic;/dashboardprotected - Redirect with
?orstate.fromfor return URL - Logged-in user visiting
/login→ redirect home - 401 from API clears token and navigates to login
FAQ
beforeEach equivalent?
No global hook—use ProtectedRoute, layout wrappers, or loader redirects.
Lazy + TypeScript default export?
const Page = lazy(() => import("./Page")); // Page must export defaultNamed export:
lazy(() => import("./Page").then((m) => ({ default: m.Page })));Suspense with data fetching?
React 19 use and frameworks like TanStack Query—start with hook + loading state in chapter 16.
Chunk load failed on deploy?
Users on old tabs after deploy—catch dynamic import error and prompt refresh.