In traditional multi-page web applications, navigating via standard hyperlinks completely destroyed the current page: the browser discarded the document, requested new HTML, parsed CSS stylesheets, executed scripts from scratch, and reset all form inputs, scroll positions, and opened modals.
Next.js 16 App Router solves this fundamental UX challenge using two deeply coupled mechanisms:
layout.tsx): Shared UI wrappers (Header, Sidebar, Navigation Shell) that never unmount or re-render during child navigation, preserving their client state (Preserved State) via Subtree Reconciliation. Clicking an internal link only updates and renders the leaf page.tsx component.<Link> Component: A drop-in navigation component enabling seamless "Soft Navigation" via the HTML5 History API without browser tab refreshes. It automatically performs background prefetching of the destination route's Flight payload and JavaScript chunks the moment a link enters the viewport.Here is how rendering and state preservation behave during page transitions:
+-------------------------------------------------------------------------+
| STATE PRESERVATION IN NESTED LAYOUTS |
+-------------------------------------------------------------------------+
| [RootLayout: app/layout.tsx] -----------------------------------------+ |
| | Global Header, Cart, Theme Provider (NEVER RE-RENDER ON NAVIGATION) | |
| | | |
| | [CatalogLayout: app/catalog/layout.tsx] --------------------------+ | |
| | | Category Filters, Sidebar (PRESERVE THEIR INTERNAL LOCAL STATE!)| | |
| | | | | |
| | | Navigating from /catalog/phones to /catalog/laptops: | | |
| | | ONLY THE LEAF COMPONENT SWAPS: | | |
| | | Previous: [Page: PhonesPage] | | |
| | | Current: [Page: LaptopsPage] | | |
| | +-----------------------------------------------------------------+ | |
| +---------------------------------------------------------------------+ |
+-------------------------------------------------------------------------+The browser IntersectionObserver detects the <Link> component entering the user visible screen area.
Next.js quietly downloads the server Flight payload of the destination route into the in-memory Router Cache.
The onClick handler intercepts the event (calling preventDefault()) and checks the primed Router Cache.
Parent layout.tsx maintains its DOM nodes and state, while the new page.tsx mounts instantly without reload.
A user types a search query into an input field inside `app/catalog/layout.tsx` and clicks a link navigating to another catalog category. What happens to the query string in the input?
app/layout.tsx imports global fonts (next/font), stylesheets, and hosts the top-level <html> and <body> tags.template.tsx instead of layout.tsx when CSS entry transitions or analytics page-view triggers must run on every transition.app/layout.tsx#A root layout is required for every Next.js application. It is the single place where the <html> and <body> tags must be declared:
import type { Metadata } from 'next';
import Link from 'next/link';
import '@/app/globals.css';
export const metadata: Metadata = {
title: 'WPSKILLS Platform',
description: 'Next-generation engineering learning platform',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className="dark">
<body className="bg-bg-base text-text-primary min-h-screen flex flex-col antialiased">
{/* Global header (persists across all page transitions) */}
<header className="border-b border-border p-4 bg-bg-surface/80 backdrop-blur-md sticky top-0 z-50">
<nav className="max-w-6xl mx-auto flex items-center justify-between">
<Link href="/" className="font-bold text-accent-cyan text-lg">
WPSKILLS
</Link>
<div className="flex gap-4 text-sm font-medium">
<Link href="/catalog" className="hover:text-accent-cyan transition-colors">
Catalog
</Link>
<Link href="/dashboard" className="hover:text-accent-cyan transition-colors">
Dashboard
</Link>
</div>
</nav>
</header>
{/* Child pages and nested sub-layouts mount here */}
<main className="flex-1">{children}</main>
<footer className="border-t border-border p-6 text-center text-xs text-text-muted">
© 2026 WPSKILLS. All rights reserved.
</footer>
</body>
</html>
);
}<Link> Component#The <Link> component from next/link enhances standard anchor elements:
import Link from 'next/link';
export function NavigationCards() {
return (
<div className="grid grid-cols-2 gap-4">
{/* Soft navigation with automatic prefetching by default */}
<Link
href="/catalog/typescript"
className="p-6 rounded-xl border border-border bg-bg-surface hover:border-accent-cyan transition-all"
>
<h3 className="font-bold">TypeScript Mastery →</h3>
<p className="text-sm text-text-muted mt-1">Master strict static typing</p>
</Link>
{/* Disabling prefetch for rarely visited or heavy reporting routes */}
<Link
href="/admin/heavy-reports"
prefetch={false}
className="p-6 rounded-xl border border-border bg-bg-surface hover:border-accent-violet transition-all"
>
<h3 className="font-bold">Heavy Reports (No Prefetch) →</h3>
</Link>
</div>
);
}layout.tsx and template.tsx#When you need a wrapper to completely remount on every route change (for example, to trigger entering animations or reset form state), use template.tsx:
'use client';
import React from 'react';
// template.tsx creates a FRESH DOM instance on every route transition!
export default function CatalogTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="animate-in fade-in duration-300">
{children}
</div>
);
}When should an engineer intentionally choose template.tsx over layout.tsx?
Tags <html> and <body> are permitted strictly once in the entire application tree — inside root app/layout.tsx. Attempting to add them inside nested app/catalog/layout.tsx causes DOM hierarchy conflicts and fatal hydration errors.
Junior developers frequently write <div onClick={() => router.push('/path')}>.
Why this is harmful:
href attribute in the DOM (damaging SEO);<Link> components!