404 Not Found

404 Not Found


nginx

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


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.

TSX
// 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

(1) Basic Usage

100%
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
TSX
// ============================================
// 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:

TEXT
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

TSX
// ============================================
// 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:

TEXT
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

100%
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

TSX
// ============================================
// 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:

TEXT
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

TSX
// ============================================
// 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:

TEXT
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

100%
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

TSX
// ============================================
// 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:

TEXT
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

TSX
// ============================================
// 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:

TEXT
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

100%
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

TSX
// ============================================
// 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:

TEXT
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

TSX
// ============================================
// <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:

TEXT
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

100%
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

TSX
// ============================================
// 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:

TEXT
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:

100%
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

TSX
// ============================================
// 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:

TEXT
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

100%
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

TSX
// ============================================
// 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:

TEXT
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

TSX
// ============================================
// 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:

TEXT
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

Q What is the difference between the Link component and the &lt;a&gt; tag?
A The Link component handles routing on the client side without triggering a full page refresh, preserves the layout, and supports preloading. The &lt;a&gt; tag triggers a full page reload (hard navigation), causing all React state to be lost. Next.js uses Link for all internal routing.
Q Why do components that use useRouter need to include 'use client'?
A 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.
Q What is the difference between router.refresh() and window.location.reload()?
A 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.
Q Does Link's prefetch also trigger on mobile devices?
A Yes. Prefetch triggers for links within the viewport, including on mobile devices. However, Next.js takes the user's data usage into account—it may not prefetch large pages on slow networks (2G/3G). You can also manually control this using prefetch={false}.
Q When should redirect() and <Redirect> be used?
A 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.
Q What should I do if the URL changes but the page content remains the same while navigating?
A This is usually caused by using the same React key or caching. You can try the following: 1) Use a different key in the page component to force a rebuild; 2) Call router.refresh() to refresh the server-side data; 3) Check whether the data is being cached by the layout.

📖 Summary


📝 Exercises

  1. 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.

  2. 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, use redirect() to return to the login page and include the error parameters.

  3. 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. Use router.back() to implement the back navigation.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏