404 Not Found

404 Not Found


nginx

Navigation: Link & Programmatic 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

Output:

TEXT
Diagram of client-side navigation using Link component.
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
Renders: Navigation links using Next.js Link component for client-side routing.

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

Output:

TEXT
The page renders as described above, with the UI updating based on the described 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
Maps over products to render list.
Content: Products

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

Output:

TEXT
Diagram: Navigation Trigger; Scroll to the top of the new page; Keep the current 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
Maps over products to render list.
Content: Product Catalog

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

Output:

TEXT
The component renders the described UI in the browser.
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
Content: Checkout | Place Order | After placing order, back button wi

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

Output:

TEXT
Diagram of client-side navigation using Link component.
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
NavigationBar renders its UI.

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

Output:

TEXT
The page renders as described above, with the UI updating based on the described behavior.
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
Form with: name.
Content: Create Project

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

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

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

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%

🙏 帮我们做得更好

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

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