Navigation Solutions: The Complete Guide to the Link Component and the Programmatic Navigation API
Navigation is like a subway system—there are automated lines (Link components), options for manual control (programmatic navigation), express trains for quick commutes (soft navigation), and transfer routes (hard navigation).
1. What You'll Learn
- Core uses of the
<Link>component:prefetch,scroll,replace useRouterProgrammatic Navigation:push,replace,back,forward,refresh- Use Cases for the
redirect()Function and the<Redirect>Component - The Principles and Differences Between Soft Navigation and Hard Navigation
- Implementation Options for Navigation Loading Indicators
2. A True Story of a Junior Developer
(1) Pain Point: Poor navigation experience between pages
Charlie has recently been developing an e-commerce website using Next.js and has found that the navigation experience is poor:
"When a user clicks a product link from the home page, the page goes blank for 1 second before loading. Every time the user clicks 'Back,' the entire page reloads. Plus, there's a bug—if a user fills out half a form, accidentally clicks a product link, and then clicks 'Back,' all the form data is lost."
Specific question:
| Issue | Impact | User Feedback |
|---|---|---|
| Blank screen when switching pages | Clunky navigation | "It takes forever to click on a link" |
| Form Data Loss | User Re-entered Data | "I filled it out halfway, but when I went back, it was gone" |
| Preloading is not smart | Does not preload frequently visited pages | "The slowest transition is from the home page to the product details page" |
| Scroll Position Reset | Return to the top after paginating on the list page | "Have to scroll back down every time" |
(2) Solutions for Next.js Navigation
Link component auto-fetch + Soft Navigation state preservation + scroll={false} to preserve scroll position.
// Optimized Navigation on the Product List Page
<Link
href={`/products/${product.id}`}
prefetch={true}
scroll={false}
className="block p-4 border rounded hover:shadow"
>
{product.title}
</Link>
(3) Revenue
| Dimension | Before Optimization (Standard <a> Tag) |
After Optimization (<Link> Component) |
|---|---|---|
| Page Load Speed | 1–2 seconds (blank screen) | Instant (pre-fetched cache) |
| Form Data Persistence | Lost when the page is unloaded | Persisted in Soft Nav |
| Scroll Position Retained | Back to Top | Exact Retention |
| User Experience Rating | 3.2/5 | 4.8/5 |
3. Link Component
(1) Basic Usage
graph LR
A[Link Components] --> B[Client Navigation<br/>No full-page refresh]
A --> C[Automatic Pre-fetching<br/>viewport Links within]
A --> D[Scroll Control<br/>scroll={false}]
A --> E[Replacement History<br/>replace]
style A fill:#cce5ff
style B fill:#d4edda
▶ Example: Link Basic Navigation
// ============================================
// Link Basic Usage of Components
// ============================================
import Link from "next/link";
export default function Navigation() {
return (
<nav className="flex gap-6 p-4 bg-white shadow-sm">
{/* Basic Navigation */}
<Link href="/" className="text-blue-600 hover:underline">
Home
</Link>
{/* Dynamic Routing */}
<Link href="/products/42" className="text-blue-600 hover:underline">
Product 42
</Link>
{/* With query parameters */}
<Link
href="/products?category=electronics&sort=price"
className="text-blue-600 hover:underline"
>
Electronics
</Link>
{/* Complete URL */}
<Link
href="https://help.example.com"
className="text-blue-600 hover:underline"
>
Help Center
</Link>
</nav>
);
}
Output:
Browser Display 4 A blue link:
Home → /
Product 42 → /products/42
Electronics → /products?category=electronics&sort=price
Help Center → https://help.example.com(External Links)
(2) prefetch
By default, the Link component preloads links within the viewport. The preloading behavior differs between the server and the client:
| Environment | prefetch={true} |
prefetch={false} |
|---|---|---|
| Server-side rendering | Prefetch page data and RSC payload | Do not prefetch |
| Static Page | Preload Entire Page | Do Not Preload |
| Within the viewport | Default behavior | Never preload |
▶ Example: Controlling prefetch Behavior
// ============================================
// Control Link Prefetch Behavior
// ============================================
import Link from "next/link";
export default function ProductList() {
const products = [
{ id: 1, name: "Wireless Mouse" },
{ id: 2, name: "Keyboard" },
{ id: 3, name: "Monitor" },
];
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Products</h1>
{/* Default prefecth:viewport Automatically preload links within the page */}
{products.map(p => (
<Link
key={p.id}
href={`/products/${p.id}`}
className="block p-4 border-b hover:bg-gray-50"
>
{p.name}
</Link>
))}
{/* Turn off prefetching:Suitable for pages that aren't used very often */}
<Link
href="/archive"
prefetch={false}
className="block mt-4 text-gray-500"
>
View Archive (older products)
</Link>
{/* Forced Prefetch:Suitable for the next page you're about to visit */}
<Link
href="/checkout"
prefetch={true}
className="block mt-4 px-6 py-2 bg-blue-600 text-white text-center rounded"
>
Proceed to Checkout
</Link>
</div>
);
}
Output:
After the page loads,This can be observed in the network console.:
1. /products/1, /products/2, /products/3 Pre-fetched (within the viewport)
2. /archive Not pre-fetched(prefetch={false})
3. /checkout Pre-fetched(prefetch={true},Even when not there viewport)
When a user clicks the link:
- Pre-fetched pages → Real-time Display(Read from cache)
- Pages Not Prefetched → Displayed after the network request
(3) Scroll Controls
graph LR
A[Navigation Trigger] --> B{scroll Properties}
B -->|scroll=true Default| C[Scroll to the top of the new page]
B -->|scroll=false| D[Keep the current scroll position]
style C fill:#f8d7da
style D fill:#d4edda
▶ Example: scroll={false} to maintain the scroll position
// ============================================
// Product List + Modal Window:scroll=false Keep the list in place
// Users click on a product to view its details,The list does not scroll when returning
// ============================================
import Link from "next/link";
export default function ProductGrid() {
const products = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
name: `Product ${i + 1}`,
}));
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-6">Product Catalog</h1>
<div className="grid grid-cols-4 gap-4">
{products.map(p => (
<Link
key={p.id}
href={`/products/${p.id}`}
scroll={false}
className="border p-4 rounded hover:shadow-lg"
>
<div className="h-32 bg-gray-100 rounded" />
<p className="mt-2 font-medium">{p.name}</p>
</Link>
))}
</div>
</div>
);
}
Output:
1. The user has scrolled to the 3 Items per Page(Scrolled down 2000px)
2. Click on the 15 Go to the product details page
3. Click the browser's Back button
4. Back to the list page,Keep the scroll position at 2000px(No "Back to Top")
5. Comparison:If not scroll={false},Return to the top each time
(4) replace: Replace history
| Action | push (default) |
replace |
|---|---|---|
| Browser History | Add a new entry | Replace the current entry |
| Back Button Behavior | Return to the previous page | Go to the page that was replaced |
| After form submission | Cannot return to the form page | Can return (bypassing the form page) |
▶ Example: Using replace in a form scenario
// ============================================
// replace Replacement History:You cannot return to the form page after submitting.
// ============================================
import Link from "next/link";
export default function CheckoutPage() {
return (
<div className="max-w-md mx-auto p-8">
<h1 className="text-2xl font-bold mb-6">Checkout</h1>
<div className="space-y-4">
<input placeholder="Card number" className="w-full p-3 border rounded" />
<input placeholder="Expiry date" className="w-full p-3 border rounded" />
<input placeholder="CVV" className="w-full p-3 border rounded" />
</div>
{/* Usage replace:Once submitted, you cannot return to this page. */}
<Link
href="/order-confirmation"
replace
className="block mt-6 w-full p-3 bg-blue-600 text-white text-center rounded"
>
Place Order
</Link>
<p className="text-sm text-gray-500 mt-2 text-center">
After placing order, back button will skip this page
</p>
</div>
);
}
Output:
User Flow:
1. Home → Shopping Cart → Checkout → Confirmation Page
2. Tap on the checkout page "Place Order"
3. The browser address changes to /order-confirmation
4. The user clicks the Back button
5. Skip the checkout page,Go directly to the shopping cart page
6. Prevent users from accidentally returning to the checkout page and submitting the order again
4. Programmatic Navigation with useRouter
(1) API Quick Reference
graph TB
A[useRouter] --> B[push(url) - Navigate to a new page]
A --> C[replace(url) - Replace Current History]
A --> D[back() - Back]
A --> E[forward() - Forward]
A --> F[refresh() - Refresh this page]
A --> G[prefetch(url) - Programmatic Prefetching]
style A fill:#cce5ff
| Method | Parameters | Description | Browser Behavior |
|---|---|---|---|
push |
href: string |
Navigate to the new URL | Add to history |
replace |
href: string |
Replace Current History | Replace History |
back |
None | Browser Back | Same as history.back() |
forward |
None | Browser Forward | Same as history.forward() |
refresh |
None | Refresh this page | Server-Side Rendering (RSC) |
prefetch |
href: string |
Prefetch Page | Cache RSC Payload |
▶ Example: Programmatic Navigation
// ============================================
// useRouter Complete Example of Programmatic Navigation
// ============================================
'use client';
import { useRouter } from "next/navigation";
export default function NavigationBar({ userId }: { userId: string }) {
const router = useRouter();
return (
<div className="p-4 bg-white shadow-sm">
<div className="flex gap-4 max-w-4xl mx-auto">
{/* push:Go to the Home Page */}
<button
onClick={() => router.push("/")}
className="px-4 py-2 bg-blue-600 text-white rounded"
>
Home
</button>
{/* push:Dynamic Routing */}
<button
onClick={() => router.push(`/users/${userId}`)}
className="px-4 py-2 bg-gray-600 text-white rounded"
>
Profile
</button>
{/* replace:Replace the current page */}
<button
onClick={() => router.replace("/login")}
className="px-4 py-2 bg-red-600 text-white rounded"
>
Logout
</button>
{/* back/forward:Browsing History */}
<button
onClick={() => router.back()}
className="px-4 py-2 border rounded"
>
← Back
</button>
<button
onClick={() => router.forward()}
className="px-4 py-2 border rounded"
>
Forward →
</button>
{/* refresh:Refresh this page(Server-Side Re-rendering) */}
<button
onClick={() => router.refresh()}
className="px-4 py-2 border rounded"
>
Refresh ↻
</button>
</div>
</div>
);
}
Output:
Click each button:
[Home] → Navigate to /
[Profile] → Navigate to /users/123
[Logout] → Replace the current history with /login(No Return)
[← Back] → Browser Back Button
[Forward] → Browser Forward
[Refresh] → Refresh this page(RSC Render Again,Does not cause the entire page to reload)
▶ Example: Navigation After Form Submission
// ============================================
// Form Submission:Verification → save → Programmatic Navigation
// ============================================
'use client';
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function CreateProjectForm() {
const router = useRouter();
const [saving, setSaving] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
try {
// Simulation API Call
await new Promise(resolve => setTimeout(resolve, 1500));
// After successful completion, navigate to the new project page
router.push("/projects/42");
// and refresh the data cache
router.refresh();
} catch (err) {
console.error("Failed to create project:", err);
alert("Failed to create project. Please try again.");
} finally {
setSaving(false);
}
}
return (
<form onSubmit={handleSubmit} className="max-w-lg mx-auto p-8 space-y-4">
<h1 className="text-2xl font-bold">Create Project</h1>
<input
name="name"
placeholder="Project name"
className="w-full p-3 border rounded"
required
/>
<textarea
name="description"
placeholder="Description"
className="w-full p-3 border rounded h-32"
/>
<button
type="submit"
disabled={saving}
className="w-full p-3 bg-blue-600 text-white rounded disabled:opacity-50"
>
{saving ? "Creating..." : "Create Project"}
</button>
</form>
);
}
Output:
1. Users enter the project name and description
2. Click the "Create Project" button
3. The button changes to "Creating..."(Disabled)
4. After 1.5s (simulated API delay)
5. Page Navigation to /projects/42
6. Refresh Project List Data(Includes newly created projects)
5. redirect() and <Redirect>
(1) Two Types of Redirects
graph TB
A[Redirect Requests] --> B[Server-side<br/>redirect()]
A --> C[Client<br/><Redirect>]
B --> D[Server Action / Route Handler]
B --> E[Server Component]
C --> F[Client Component]
C --> G[When using conditional rendering]
style B fill:#d4edda
style C fill:#cce5ff
| Method | Where to Use | When to Trigger | Performance |
|---|---|---|---|
redirect() |
Server Actions / Server Components | During Server Response | Server Redirection, Zero Client Overhead |
<Redirect> |
Client Components | During rendering | Client-side redirection, route changes |
▶ Example: Using redirect in a Server Action
// ============================================
// redirect() Used in a Server Action
// Login authentication failed → Redirect to the error page
// ============================================
// app/actions/auth.ts
'use server';
import { redirect } from "next/navigation";
export async function login(formData: FormData) {
const email = formData.get("email");
const password = formData.get("password");
// Simulation Validation
if (email !== "alice@example.com" || password !== "password123") {
redirect("/login?error=invalid_credentials");
}
// Redirect to the dashboard after successful completion
redirect("/dashboard");
}
Output:
After the login form is submitted:
1. Server-Side Execution login Server Action
2. Verification Failed → Sent from the server 303 Redirect to /login?error=invalid_credentials
3. Browser Redirect,No client-side code required
4. URL become /login?error=invalid_credentials
5. Display an error message (read from the URL parameter)
▶ Example: Using <Redirect> in the Client Component
// ============================================
// <Redirect> Components — Conditional Redirection
// Unregistered Users Accessing the Dashboard → Redirect
// ============================================
'use client';
import { useRouter } from "next/navigation";
export default function ProtectedPage() {
const router = useRouter();
const isLoggedIn = false; // Simulate a non-logged-in user
if (!isLoggedIn) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-red-600">
Access Denied
</h1>
<p className="text-gray-600 mt-2">
Please sign in to access this page.
</p>
<button
onClick={() => router.push("/login")}
className="mt-4 px-6 py-2 bg-blue-600 text-white rounded"
>
Go to Login
</button>
</div>
</div>
);
}
return <div>Dashboard Content</div>;
}
Output:
Access a Protected Page:
1. Detected isLoggedIn = false
2. Show "Access Denied" + "Please sign in" Presentation
3. Click "Go to Login" Go to the login page
4. For server-side redirects, use redirect("/login") instead
6. Soft vs Hard Navigation
(1) Comparison
graph TB
subgraph "Soft Navigation(Link/useRouter)"
A[Client-Side Routing] --> B[Page Navigation<br/>No full-page refresh]
B --> C[layout Keep Mounted]
B --> D[React State Preservation]
B --> E[RSC Payload Replace]
end
subgraph "Hard Navigation(Refresh the entire browser page)"
F[Full-Page Loading in the Browser] --> G[Page Navigation<br/>Refresh the entire page]
G --> H[layout Remount]
G --> I[Reset All States]
G --> J[Full Set JS/CSS Loading]
end
style A fill:#d4edda
style F fill:#f8d7da
| Characteristic | Soft Navigation | Hard Navigation |
|---|---|---|
| Trigger Method | <Link>, useRouter() |
Browser refresh, <a> tab, window.location |
| Refresh Entire Page | ❌ No | ✅ Yes |
| Layout Preserved | ✅ Preserved | ❌ Remounted |
| RSC Payload | Incremental Update | Full Download |
| Performance | Real-time | 500 ms–2 s |
| Status Maintained | ✅ | ❌ |
▶ Example: Soft Navigation Demo
// ============================================
// Soft Navigation vs Hard Navigation Comparison
// ============================================
// app/demo/page.tsx
'use client';
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
export default function NavDemo() {
const router = useRouter();
const [formData, setFormData] = useState({
name: "Alice",
email: "alice@example.com",
});
return (
<div className="max-w-2xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">Navigation Demo</h1>
{/* Form Data */}
<div className="bg-yellow-50 border border-yellow-200 p-4 rounded mb-6">
<p className="text-sm font-medium">Form State (check after navigation):</p>
<pre className="mt-2 text-sm">
{JSON.stringify(formData, null, 2)}
</pre>
</div>
<input
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
className="w-full p-3 border rounded mb-2"
/>
<input
value={formData.email}
onChange={e => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
className="w-full p-3 border rounded mb-4"
/>
<div className="flex gap-4">
{/* Soft Navigation — State Preservation */}
<Link
href="/demo/page-a"
className="px-4 py-2 bg-blue-600 text-white rounded"
>
Soft Nav (Link)
</Link>
{/* Hard Navigation — State Loss */}
<a
href="/demo/page-a"
className="px-4 py-2 bg-red-600 text-white rounded"
>
Hard Nav (a tag)
</a>
</div>
<p className="text-sm text-gray-500 mt-4">
Note: Modify the form data, then try both navigation methods.
<br />
<strong>Link</strong> keeps form state. <strong>a tag</strong> loses it.
</p>
</div>
);
}
Output:
1. Edit Name to "Bob Jenkins"
2. Click "Soft Nav (Link)" → Navigate to /demo/page-a
3. Click the browser's Back button → Form Data Persistence "Bob Jenkins" ✅
4. Edit Name to "Charlie"
5. Click "Hard Nav (a tag)" → Refresh the entire page to /demo/page-a
6. Click the browser's Back button → Reset form data to "Alice" ❌
(2) Special Behavior of router.refresh()
router.refresh() is a special type of soft navigation—it does not change the URL, but causes the server to re-render the RSC components on the current page:
graph LR
A[router.refresh] --> B[Preserving Client State<br/>useState/Context]
A --> C[Retrieve server data again<br/>RSC Render Again]
A --> D[UI Update<br/>Replace old data with new data]
A --> E[URL Unchanged]
style A fill:#d4edda
▶ Example: refresh to update the data
// ============================================
// router.refresh() — No change URL Page refresh
// Applicable to:Server Action Refresh the data after submitting
// ============================================
'use client';
import { useRouter } from "next/navigation";
export default function ProjectActions({ projectId }: { projectId: string }) {
const router = useRouter();
async function deleteProject() {
// Call Server Action Delete Item
await fetch(`/api/projects/${projectId}`, { method: "DELETE" });
// Refresh the server-side data for the current page(No change URL)
router.refresh();
// The page is now being reloaded,Deleted items are no longer included in the project list
}
return (
<button
onClick={deleteProject}
className="px-4 py-2 bg-red-600 text-white rounded"
>
Delete Project
</button>
);
}
Output:
1. List of Page Rendering Projects(From the server fetch)
2. The user clicks the "Delete Project" button
3. API Call to Delete Item
4. router.refresh() Trigger a server-side re-render
5. Project List Update(Deleted items are no longer displayed)
6. The page does not refresh entirely,Client State Persistence
7. Navigation Loading Indicator
(1) Implementation Plan
graph TB
A[Navigation Trigger] --> B[Loading Indicator]
B --> C[Top Progress Bar<br/>NProgress Style]
B --> D[Suspense fallback<br/>Wireframe Display]
B --> E[loading.tsx<br/>Page-Level Loading]
style A fill:#cce5ff
style B fill:#f8d7da
▶ Example: Top Navigation Progress Bar
// ============================================
// Custom Navigation Loading Indicator(NProgress Style)
// ============================================
'use client';
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
export function NavigationProgress() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const timerRef = useRef(null);
useEffect(() => {
const originalPush = router.push;
const originalReplace = router.replace;
function startLoading() {
setLoading(true);
timerRef.current = setTimeout(() => setLoading(false), 10000);
}
function stopLoading() {
setLoading(false);
if (timerRef.current) clearTimeout(timerRef.current);
}
// Note:This is a simplified demonstration.,In practice, a more robust implementation is needed.
// For production use, consider next/navigation's useNavigationEvent
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [router]);
return (
<div
className={`fixed top-0 left-0 h-1 bg-blue-600 transition-all duration-300 z-50 ${
loading ? "w-full opacity-100" : "w-0 opacity-0"
}`}
/>
);
}
Output:
When navigation is triggered:
1. A thin blue line (4px tall) appears at the top of the browser
2. The progress bar starts at 0% Quick Fill to 100%
3. The progress bar disappears once the page has finished loading
4. The entire animation is smooth,Flicker-free
5. The user knows they are navigating
8. Complete Example: E-commerce Navigation System
// ============================================
// Comprehensive Example:A Comprehensive Navigation System for E-commerce
// Covering Link、useRouter、redirect、Navigation Loading State
// ============================================
// src/app/layout.tsx — Global Navigation Bar
import Link from "next/link";
import { CartCount } from "./CartCount";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header className="bg-white shadow-sm sticky top-0 z-40">
<div className="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-blue-600">
ShopHub
</Link>
<nav className="hidden md:flex gap-6">
<Link href="/products" className="hover:text-blue-600">
Products
</Link>
<Link href="/categories" className="hover:text-blue-600">
Categories
</Link>
<Link href="/deals" prefetch={false} className="hover:text-blue-600">
Deals
</Link>
<Link href="/about" className="hover:text-blue-600">
About
</Link>
</nav>
<div className="flex items-center gap-4">
<Link href="/search" className="text-gray-600 hover:text-blue-600">
Search
</Link>
<Link href="/cart" className="relative text-gray-600 hover:text-blue-600">
Cart
<CartCount />
</Link>
<Link
href="/account"
className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm"
>
Sign In
</Link>
</div>
</div>
</header>
<main className="max-w-6xl mx-auto px-4 py-8">
{children}
</main>
</body>
</html>
);
}
// src/app/products/[id]/page.tsx — Product Details(Includes navigation logic)
'use client';
import { useRouter } from "next/navigation";
import Link from "next/link";
export default function ProductDetail({ params }) {
const router = useRouter();
async function handleAddToCart() {
await fetch("/api/cart", {
method: "POST",
body: JSON.stringify({ productId: params.id }),
});
// Refresh the quantity in the shopping cart after adding an item
router.refresh();
}
async function handleBuyNow() {
await fetch("/api/cart", {
method: "POST",
body: JSON.stringify({ productId: params.id, quantity: 1 }),
});
// Buy Now → Proceed to Checkout (use replace to avoid returning to the product page)
router.replace("/checkout");
}
return (
<div>
{/* Breadcrumb Navigation */}
<nav className="text-sm text-gray-500 mb-6">
<Link href="/" className="hover:text-blue-600">Home</Link>
<span className="mx-2">/</span>
<Link href="/products" className="hover:text-blue-600">Products</Link>
<span className="mx-2">/</span>
<span className="text-gray-900">{params.id}</span>
</nav>
<div className="flex gap-8">
<div className="w-1/2">
<img
src={`https://picsum.photos/seed/${params.id}/400/400`}
alt="Product"
className="w-full rounded-lg"
/>
</div>
<div className="w-1/2">
<h1 className="text-3xl font-bold">Product #{params.id}</h1>
<p className="text-2xl text-green-600 font-bold mt-4">$49.99</p>
<p className="text-gray-600 mt-4">
High-quality product with premium features.
</p>
<div className="flex gap-4 mt-8">
<button
onClick={handleAddToCart}
className="flex-1 px-6 py-3 border-2 border-blue-600 text-blue-600 rounded-lg hover:bg-blue-50"
>
Add to Cart
</button>
<button
onClick={handleBuyNow}
className="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Buy Now
</button>
</div>
<div className="mt-8 border-t pt-6">
<Link
href={`/products/${Number(params.id) - 1}`}
scroll={false}
className="text-blue-600 hover:underline"
>
← Previous Product
</Link>
<Link
href={`/products/${Number(params.id) + 1}`}
scroll={false}
className="text-blue-600 hover:underline float-right"
>
Next Product →
</Link>
</div>
</div>
</div>
</div>
);
}
// src/app/CartCount.tsx — Shopping Cart Quantity Display
export async function CartCount() {
const cart = await fetch("https://api.example.com/cart");
const itemCount = cart?.items?.length ?? 0;
if (itemCount === 0) return null;
return (
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
{itemCount}
</span>
);
}
Expected Output:
Navigation Bar(Fixed at the top):
[ShopHub] Products Categories Deals About [Search] Cart(3) [Sign In]
Product Details Page(/products/1):
Home / Products / 1
[Product Images] [Product Details]
Product #1
$49.99
[Add to Cart] [Buy Now]
← Previous Product | Next Product →
Navigation Behavior:
- Click Products → Soft Nav,Instant Page Switching
- Click Deals → No advance pickup(prefetch=false),But navigation is still fast
- Add to Cart → router.refresh(),Shopping Cart Logo Update
- Buy Now → router.replace("/checkout"),Replacement History
❓ FAQ
Link component and the <a> tag?Link component handles routing on the client side without triggering a full page refresh, preserves the layout, and supports preloading. The <a> tag triggers a full page reload (hard navigation), causing all React state to be lost. Next.js uses Link for all internal routing.useRouter need to include 'use client'?useRouter is a React hook and can only be used in client components. Server components do not have access to browser APIs (such as history and location), so routing-related hooks are executed on the client side.router.refresh() and window.location.reload()?router.refresh() is Next.js’s soft refresh—it re-requests the server-side RSC payload, updating only the changed parts while preserving client-side state (such as useState and Context). window.location.reload() performs a full page refresh, causing all state to be lost and requiring a full reload of JS and CSS.prefetch={false}.redirect() and <Redirect> be used?redirect() is used on the server side (Server Actions, Server Components, Route Handlers) to send a 303/307 response. For client-side conditional redirection, use access control logic (display an “Access Denied” message + a navigation button) instead of the <Redirect> component.router.refresh() to refresh the server-side data; 3) Check whether the data is being cached by the layout.📖 Summary
- The
<Link>component is the preferred navigation solution for Next.js, supporting automatic preloading and client-side transitions prefetch={true}Preload links within the viewport,prefetch={false}Disable preloadingscroll={false}Maintains scroll position; suitable for list pages and detail pagesreplaceOverride the browser history to prevent accidental navigation back from form pagesuseRouterProvides programmatic navigation:push,replace,back,forward,refreshredirect()For use on the server side; sends a 303/307 redirect response- Soft Navigation preserves the layout and state, while Hard Navigation refreshes the entire page
router.refresh()is a special soft navigation feature: it refreshes RSC data without changing the URL
📝 Exercises
-
Basic Question (⭐): Create five links on the page (Home, About, Contact, Product List, Product Details #42), and observe which URLs are preloaded in the browser’s Network tab.
-
Advanced Exercise (⭐⭐): Implement a "Login → Dashboard" workflow: Use a Server Action to verify the login; if successful, use
redirect()to redirect to the dashboard; if unsuccessful, useredirect()to return to the login page and include the error parameters. -
Challenge (⭐⭐⭐): Implement an infinite-scrolling list page. When a user clicks on a product to go to the details page, use
scroll={false}to preserve the scroll position; when the user returns, the list should remain at its previous position. Userouter.back()to implement the back navigation.



