最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Next.js從入門到精通的萬字長(zhǎng)文教程(附詳細(xì)代碼)

 更新時(shí)間:2025年11月29日 09:43:46   作者:油墨香^_^  
Next.js是一個(gè)基于React的框架,提供了服務(wù)器端渲染(SSR)和靜態(tài)站點(diǎn)生成(SSG)功能,這篇文章主要介紹了Next.js從入門到精通的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

引言:為什么是 Next.js?

在 React 生態(tài)系統(tǒng)中,我們常常面臨一些挑戰(zhàn):如何配置服務(wù)器端渲染(SSR)以提升首屏加載速度和 SEO?如何實(shí)現(xiàn)靜態(tài)站點(diǎn)生成(SSG)以獲得極致的性能?如何優(yōu)雅地處理路由、API 接口、代碼分割和打包優(yōu)化?

Next.js 的出現(xiàn),完美地回答了這些問題。它是由 Vercel 公司創(chuàng)建的基于 React 的全功能生產(chǎn)級(jí)框架。它提供了一系列開箱即用的特性,讓你能專注于業(yè)務(wù)邏輯而非構(gòu)建配置。從簡(jiǎn)單的個(gè)人博客到復(fù)雜的電子商務(wù)平臺(tái),Next.js 都能勝任。

Next.js 的核心優(yōu)勢(shì):

  1. 零配置: 內(nèi)置 webpack、Babel、代碼分割、熱更新等,開箱即用。

  2. 服務(wù)端渲染: 輕松實(shí)現(xiàn) SSR 和 SSG,提升性能和 SEO。

  3. 文件系統(tǒng)路由: 在 pages 目錄下創(chuàng)建文件即可自動(dòng)生成路由。

  4. API 路由: 在同一個(gè)項(xiàng)目中編寫前端和后端 API。

  5. 出色的開發(fā)者體驗(yàn): 快速刷新、強(qiáng)大的錯(cuò)誤提示等。

  6. 強(qiáng)大的社區(qū)和生態(tài): 擁有龐大的社區(qū)支持和豐富的插件。

本教程將分為四個(gè)部分:

  1. 入門篇: 創(chuàng)建第一個(gè) Next.js 應(yīng)用,理解核心概念。

  2. 核心概念篇: 深入探討路由、數(shù)據(jù)獲取、樣式和 API 路由。

  3. 進(jìn)階實(shí)戰(zhàn)篇: 構(gòu)建一個(gè)完整的全棧博客應(yīng)用。

  4. 精通與優(yōu)化篇: 探索高級(jí)特性、性能優(yōu)化和部署。

第一部分:入門篇 - 創(chuàng)建你的第一個(gè) Next.js 應(yīng)用

1.1 環(huán)境準(zhǔn)備與項(xiàng)目創(chuàng)建

首先,確保你的系統(tǒng)安裝了 Node.js (版本 14.6.0 或更高)。

Next.js 提供了官方的創(chuàng)建工具 create-next-app,它能快速搭建一個(gè)預(yù)配置好的 Next.js 項(xiàng)目。

打開你的終端,執(zhí)行以下命令:

bash

npx create-next-app@latest my-nextjs-app

你會(huì)被提示選擇一些配置項(xiàng):

  • Would you like to use TypeScript? No / Yes (推薦選擇 Yes)

  • Would you like to use ESLint? Yes (推薦)

  • Would you like to use Tailwind CSS? No / Yes (根據(jù)喜好,這里我們先選 No)

  • Would you like to use src/ directory? No / Yes (推薦 Yes,保持項(xiàng)目結(jié)構(gòu)清晰)

  • Would you like to use App Router? Yes (推薦,這是 Next.js 13+ 的現(xiàn)代路由)

我們選擇使用 App Router,它是 Next.js 13 引入的基于 React Server Components 的新路由系統(tǒng),功能更強(qiáng)大,是未來的方向。

等待依賴安裝完成后,進(jìn)入項(xiàng)目目錄并啟動(dòng)開發(fā)服務(wù)器:

bash

cd my-nextjs-app
npm run dev

在瀏覽器中打開 http://localhost:3000,你將看到 Next.js 的歡迎頁(yè)面。

1.2 項(xiàng)目結(jié)構(gòu)初探

使用 create-next-app 并選擇上述配置后,你的項(xiàng)目結(jié)構(gòu)大致如下:

text

my-nextjs-app/
├── app/                 # App Router 的主目錄
│   ├── favicon.ico
│   ├── globals.css      # 全局樣式
│   ├── layout.tsx       # 根布局組件
│   ├── page.tsx         # 首頁(yè)組件 (對(duì)應(yīng)路由 `/`)
│   └── ...
├── public/              # 靜態(tài)資源目錄 (圖片、字體等)
│   └── ...
├── next.config.js       # Next.js 配置文件
├── package.json
├── eslint.config.mjs    # ESLint 配置
└── tsconfig.json        # TypeScript 配置

關(guān)鍵文件解釋:

  • app/layout.tsx: 這是根布局。所有頁(yè)面都會(huì)共享這個(gè)布局中的內(nèi)容(如 <html> 和 <body> 標(biāo)簽)。它是 Server Component。

  • app/page.tsx: 這是你的首頁(yè)。當(dāng)你訪問 / 時(shí),渲染的就是這個(gè)組件。

  • public/: 這個(gè)目錄下的文件可以被直接訪問。例如,public/logo.png 可以通過 /logo.png 在瀏覽器中訪問。

1.3 理解 React Server Components vs Client Components

這是 App Router 的核心概念。

  • Server Components (默認(rèn)):

    • 在服務(wù)器上運(yùn)行。 你的組件代碼不會(huì)被打包發(fā)送到客戶端。

    • 可以直接訪問后端資源。 如數(shù)據(jù)庫(kù)、API 密鑰、文件系統(tǒng),無需通過 API 路由。

    • 有助于減少客戶端包大小。 因?yàn)榇a在服務(wù)端執(zhí)行。

    • 不能使用狀態(tài)和生命周期。 不能使用 useStateuseEffect 等 Hook。

    • 不能使用瀏覽器 API。 如 windowdocument。

  • Client Components:

    • 在客戶端(瀏覽器)運(yùn)行。 和傳統(tǒng)的 React 組件一樣。

    • 可以使用狀態(tài)和生命周期。 可以使用所有 React Hook。

    • 可以處理交互性。 如點(diǎn)擊事件、表單提交。

    • 需要使用 "use client" 指令。 在文件頂部聲明。

示例: 讓我們修改 app/page.tsx,看看它們?nèi)绾螀f(xié)作。

tsx

// app/page.tsx
// 這是一個(gè) Server Component (默認(rèn))

// 一個(gè)在服務(wù)器端獲取數(shù)據(jù)的異步函數(shù) (模擬)
async function fetchServerSideData() {
  // 這里可以直接訪問數(shù)據(jù)庫(kù)或內(nèi)部 API
  await new Promise(resolve => setTimeout(resolve, 1000)); // 模擬延遲
  return { message: "Hello from the Server!" };
}

// 一個(gè)普通的 Server Component
function ServerGreeting() {
  return <p>I was rendered on the server.</p>;
}

export default async function HomePage() {
  // 在 Server Component 中可以直接使用 async/await 獲取數(shù)據(jù)
  const data = await fetchServerSideData();

  return (
    <div>
      <h1>My First Next.js App</h1>
      <p>{data.message}</p>
      <ServerGreeting />
      {/* 我們引入一個(gè) Client Component */}
      <ClientCounter />
    </div>
  );
}

// --- 新建一個(gè) Client Component ---
// 在 app/components/ClientCounter.tsx
"use client"; // <-- 必須的指令,標(biāo)記這是一個(gè) Client Component

import { useState } from 'react';

export function ClientCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times (Client Component)</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

在這個(gè)例子中:

  • HomePagefetchServerSideDataServerGreeting 都在服務(wù)器端執(zhí)行。

  • ClientCounter 因?yàn)橛辛?nbsp;"use client",它是一個(gè) Client Component,其代碼會(huì)被打包發(fā)送到瀏覽器,處理點(diǎn)擊事件和狀態(tài)更新。

這種混合模式讓你能夠?yàn)轫?yè)面的每個(gè)部分選擇最合適的渲染環(huán)境,從而構(gòu)建出高性能的應(yīng)用。

第二部分:核心概念篇 - 深入 Next.js 的基石

2.1 基于文件系統(tǒng)的路由 (App Router)

在 App Router 中,路由由 app 目錄下的文件夾結(jié)構(gòu)定義。每個(gè)文件夾代表一個(gè)路由段(segment)。要?jiǎng)?chuàng)建一個(gè)頁(yè)面,你需要使用 page.tsx 或 page.jsx 文件。

基礎(chǔ)路由:

文件路徑對(duì)應(yīng)路由
app/page.tsx/
app/about/page.tsx/about
app/blog/page.tsx/blog

動(dòng)態(tài)路由:
如果你需要捕獲動(dòng)態(tài)的路由段(如博客文章 ID),可以使用方括號(hào) [folderName] 來創(chuàng)建動(dòng)態(tài)路由。

文件路徑對(duì)應(yīng)路由示例 URL
app/blog/[id]/page.tsx/blog/:id/blog/123/blog/my-post

在 page.tsx 中,你可以通過 params 屬性訪問到這個(gè)動(dòng)態(tài)參數(shù)。

tsx

// app/blog/[id]/page.tsx
interface BlogPostProps {
  params: {
    id: string;
  };
}

export default function BlogPost({ params }: BlogPostProps) {
  return (
    <div>
      <h1>Blog Post: {params.id}</h1>
      <p>This is the content of blog post with ID: {params.id}.</p>
    </div>
  );
}

布局 (Layouts):
布局是共享的 UI,它在多個(gè)頁(yè)面之間保持狀態(tài),并且不會(huì)在路由切換時(shí)重新渲染。它由 layout.tsx 文件定義。

  • 根布局 (app/layout.tsx): 必須的,它包裹所有頁(yè)面。

  • 路由組布局: 可以在任何子目錄下創(chuàng)建 layout.tsx,它只包裹該目錄下的頁(yè)面。

tsx

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {/* 一個(gè)所有頁(yè)面共享的導(dǎo)航欄 */}
        <nav className="bg-blue-600 text-white p-4">
          <div className="container mx-auto">
            <a href="/" rel="external nofollow"  className="text-xl font-bold">My Site</a>
            <a href="/about" rel="external nofollow"  className="ml-4">About</a>
            <a href="/blog" rel="external nofollow"  className="ml-4">Blog</a>
          </div>
        </nav>
        {/* 子頁(yè)面將在這里被渲染 */}
        <main className="container mx-auto p-4">{children}</main>
        {/* 一個(gè)所有頁(yè)面共享的頁(yè)腳 */}
        <footer className="bg-gray-200 p-4 text-center">
          <p>&copy; 2023 My Site</p>
        </footer>
      </body>
    </html>
  );
}

2.2 數(shù)據(jù)獲取 (Data Fetching)

Next.js 在 Server Components 中擴(kuò)展了 fetch API,提供了強(qiáng)大的數(shù)據(jù)獲取能力,包括自動(dòng)緩存和重復(fù)數(shù)據(jù)刪除。

三種渲染模式:

  1. 靜態(tài)站點(diǎn)生成: 頁(yè)面在構(gòu)建時(shí)生成。適用于內(nèi)容不經(jīng)常變化的頁(yè)面(如博客、文檔、營(yíng)銷頁(yè)面)。性能最好。

  2. 服務(wù)端渲染: 頁(yè)面在每次請(qǐng)求時(shí)生成。適用于高度動(dòng)態(tài)、個(gè)性化的頁(yè)面(如儀表盤、用戶主頁(yè))。

  3. 客戶端渲染: 頁(yè)面在瀏覽器中生成。適用于具有大量交互、不關(guān)心 SEO 的頁(yè)面部分。

在 App Router 中,你通過 fetch 的選項(xiàng)來控制渲染模式。

tsx

// app/blog/page.tsx
// 這是一個(gè) Server Component

interface Post {
  id: number;
  title: string;
  body: string;
}

// SSG (Static Generation) - 默認(rèn)行為
// 在構(gòu)建時(shí)獲取數(shù)據(jù)并生成靜態(tài)頁(yè)面
async function getStaticPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    // cache: 'force-cache' 是默認(rèn)值,等同于 SSG
  });
  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }
  return res.json();
}

// SSR (Server-Side Rendering)
// 在每次請(qǐng)求時(shí)獲取數(shù)據(jù)
async function getServerSidePosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    cache: 'no-store', // <-- 這表示不緩存,每次請(qǐng)求都重新獲取
  });
  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }
  return res.json();
}

// ISR (Incremental Static Regeneration)
// 靜態(tài)生成,但可以定期在后臺(tái)重新驗(yàn)證和更新
async function getIncrementalPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    next: { revalidate: 60 }, // <-- 每60秒重新驗(yàn)證一次
  });
  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }
  return res.json();
}

export default async function BlogListPage() {
  // 選擇一種數(shù)據(jù)獲取方式
  const posts = await getStaticPosts();

  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <a href={`/blog/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

2.3 樣式化 (Styling)

Next.js 不限定你使用何種 CSS 方案。你可以使用:

  • 全局 CSS: 在 app/globals.css 中導(dǎo)入,會(huì)應(yīng)用到所有頁(yè)面。

  • CSS Modules: 創(chuàng)建 [name].module.css 文件,樣式局部作用于組件。

  • Tailwind CSS: 功能優(yōu)先的 CSS 框架,與 Next.js 集成度極高。

  • Styled-components / Emotion: CSS-in-JS 庫(kù)(通常需要在 Client Components 中使用)。

  • Sass: 支持 .scss 和 .sass 文件。

以 CSS Modules 為例:

css

/* app/components/Button.module.css */
.primary {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.primary:hover {
  background-color: darkblue;
}

tsx

// app/components/Button.tsx
"use client"; // 如果要用 onClick,就需要是 Client Component

import styles from './Button.module.css';

interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;
}

export function Button({ children, onClick }: ButtonProps) {
  return (
    <button className={styles.primary} onClick={onClick}>
      {children}
    </button>
  );
}

2.4 API 路由 (API Routes)

Next.js 允許你在 app 目錄下創(chuàng)建 API 端點(diǎn),這意味著你可以在同一個(gè)項(xiàng)目中編寫全棧應(yīng)用。

API 路由位于 app/api/ 目錄下,使用 route.ts 文件定義。

創(chuàng)建一個(gè)簡(jiǎn)單的 GET API:

tsx

// app/api/hello/route.ts
export async function GET() {
  // 這里可以連接數(shù)據(jù)庫(kù),處理業(yè)務(wù)邏輯
  return Response.json({ message: 'Hello from the API!' });
}

現(xiàn)在,訪問 http://localhost:3000/api/hello 將會(huì)返回 JSON 數(shù)據(jù)。

處理不同的 HTTP 方法:

tsx

// app/api/users/route.ts
import { NextRequest } from 'next/server';

// GET /api/users
export async function GET() {
  const users = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
  return Response.json(users);
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();
  // 在這里,你可以將 body 中的數(shù)據(jù)保存到數(shù)據(jù)庫(kù)
  console.log('Creating user with data:', body);
  // 模擬創(chuàng)建用戶
  const newUser = { id: 3, ...body };
  return Response.json(newUser, { status: 201 });
}

你可以在 Client Component 中使用 fetch 來調(diào)用這些 API。

tsx

"use client";
// ... 在某個(gè) Client Component 中
const handleCreateUser = async () => {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Alice' }),
  });
  const newUser = await response.json();
  console.log('User created:', newUser);
};

第三部分:進(jìn)階實(shí)戰(zhàn)篇 - 構(gòu)建一個(gè)全棧博客應(yīng)用

現(xiàn)在,讓我們運(yùn)用所學(xué)的知識(shí),構(gòu)建一個(gè)簡(jiǎn)單的全棧博客應(yīng)用。它將包含:

  • 博客文章列表(SSG)

  • 單篇博客文章頁(yè)面(動(dòng)態(tài)路由 + SSG)

  • 一個(gè)用于創(chuàng)建新博客文章的 API 端點(diǎn)

3.1 項(xiàng)目結(jié)構(gòu)與數(shù)據(jù)層

我們使用一個(gè)本地的 JSON 文件來模擬數(shù)據(jù)庫(kù)。在實(shí)際項(xiàng)目中,你會(huì)使用 Prisma、Drizzle 等 ORM 連接 PostgreSQL、MySQL 等數(shù)據(jù)庫(kù)。

創(chuàng)建 lib/posts.ts 來處理數(shù)據(jù):

tsx

// lib/posts.ts
export interface Post {
  id: string;
  title: string;
  content: string;
  date: string;
}

// 模擬數(shù)據(jù)
let posts: Post[] = [
  {
    id: '1',
    title: 'First Post',
    content: 'This is the content of the first post.',
    date: '2023-10-25',
  },
  {
    id: '2',
    title: 'Second Post',
    content: 'This is the content of the second post.',
    date: '2023-10-26',
  },
];

// 獲取所有文章
export function getPosts(): Post[] {
  return posts;
}

// 根據(jù) ID 獲取文章
export function getPostById(id: string): Post | undefined {
  return posts.find(post => post.id === id);
}

// 創(chuàng)建新文章
export function createPost(post: Omit<Post, 'id'>): Post {
  const newPost: Post = {
    ...post,
    id: Date.now().toString(), // 簡(jiǎn)單的 ID 生成
  };
  posts.push(newPost);
  return newPost;
}

3.2 博客列表頁(yè)面 (SSG)

tsx

// app/blog/page.tsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';

// 這個(gè)頁(yè)面將在構(gòu)建時(shí)靜態(tài)生成
export default function BlogListPage() {
  const posts = getPosts();

  return (
    <div>
      <h1 className="text-3xl font-bold mb-8">My Blog</h1>
      <ul className="space-y-4">
        {posts.map((post) => (
          <li key={post.id} className="border-b pb-4">
            <Link href={`/blog/${post.id}`} className="text-xl font-semibold text-blue-600 hover:underline">
              {post.title}
            </Link>
            <p className="text-gray-500 text-sm">{post.date}</p>
            <p className="mt-2">{post.content.slice(0, 100)}...</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

3.3 博客文章詳情頁(yè)面 (動(dòng)態(tài)路由 + SSG)

tsx

// app/blog/[id]/page.tsx
import { notFound } from 'next/navigation';
import { getPostById, getPosts } from '@/lib/posts';

interface BlogPostProps {
  params: {
    id: string;
  };
}

// 生成靜態(tài)參數(shù),告訴 Next.js 哪些 [id] 需要在構(gòu)建時(shí)預(yù)渲染
export async function generateStaticParams() {
  const posts = getPosts();
  // 為每篇文章生成 { id: post.id }
  return posts.map((post) => ({
    id: post.id,
  }));
}

export default function BlogPostPage({ params }: BlogPostProps) {
  const post = getPostById(params.id);

  // 如果沒找到文章,返回 404 頁(yè)面
  if (!post) {
    notFound();
  }

  return (
    <article>
      <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
      <p className="text-gray-500 mb-8">{post.date}</p>
      <div className="prose max-w-none">
        {/* 假設(shè)你使用 Tailwind @tailwindcss/typography 插件來美化內(nèi)容 */}
        <p>{post.content}</p>
      </div>
    </article>
  );
}

3.4 創(chuàng)建文章的 API 端點(diǎn)

tsx

// app/api/posts/route.ts
import { NextRequest } from 'next/server';
import { createPost } from '@/lib/posts';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { title, content } = body;

    // 簡(jiǎn)單的驗(yàn)證
    if (!title || !content) {
      return Response.json({ error: 'Title and content are required' }, { status: 400 });
    }

    const newPost = createPost({
      title,
      content,
      date: new Date().toISOString().split('T')[0], // YYYY-MM-DD
    });

    return Response.json(newPost, { status: 201 });
  } catch (error) {
    return Response.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

3.5 創(chuàng)建一個(gè)表單來發(fā)布新文章 (Client Component)

tsx

// app/components/CreatePostForm.tsx
"use client";

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export function CreatePostForm() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);

    try {
      const response = await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, content }),
      });

      if (response.ok) {
        // 創(chuàng)建成功,重置表單并刷新博客列表
        setTitle('');
        setContent('');
        router.refresh(); // 刷新服務(wù)端組件的數(shù)據(jù)
        alert('Post created successfully!');
      } else {
        const error = await response.json();
        alert(`Error: ${error.error}`);
      }
    } catch (error) {
      alert('An error occurred while creating the post.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 p-4 border rounded-lg">
      <h2 className="text-2xl font-bold">Create New Post</h2>
      <div>
        <label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label>
        <input
          type="text"
          id="title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
          className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
        />
      </div>
      <div>
        <label htmlFor="content" className="block text-sm font-medium text-gray-700">Content</label>
        <textarea
          id="content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
          required
          rows={5}
          className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
        />
      </div>
      <button
        type="submit"
        disabled={isLoading}
        className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50"
      >
        {isLoading ? 'Creating...' : 'Create Post'}
      </button>
    </form>
  );
}

然后,在博客列表頁(yè)面引入這個(gè)表單:

tsx

// app/blog/page.tsx (更新)
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
import { CreatePostForm } from '@/app/components/CreatePostForm';

export default function BlogListPage() {
  const posts = getPosts();

  return (
    <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
      <div className="lg:col-span-2">
        <h1 className="text-3xl font-bold mb-8">My Blog</h1>
        <ul className="space-y-4">
          {posts.map((post) => (
            <li key={post.id} className="border-b pb-4">
              <Link href={`/blog/${post.id}`} className="text-xl font-semibold text-blue-600 hover:underline">
                {post.title}
              </Link>
              <p className="text-gray-500 text-sm">{post.date}</p>
              <p className="mt-2">{post.content.slice(0, 100)}...</p>
            </li>
          ))}
        </ul>
      </div>
      <div>
        <CreatePostForm />
      </div>
    </div>
  );
}

現(xiàn)在,你的全棧博客應(yīng)用就完成了!它具備了顯示文章、查看文章詳情和創(chuàng)建新文章的功能。

第四部分:精通與優(yōu)化篇 - 邁向生產(chǎn)環(huán)境

4.1 中間件 (Middleware)

中間件允許你在請(qǐng)求完成之前運(yùn)行代碼。它可用于身份驗(yàn)證、日志記錄、重寫 URL、修改響應(yīng)頭等。

創(chuàng)建一個(gè) middleware.ts 文件在項(xiàng)目根目錄(或 src 目錄下)。

tsx

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 檢查 cookie 或 header 來進(jìn)行簡(jiǎn)單的認(rèn)證
  const isLoggedIn = request.cookies.get('auth-token')?.value === 'secret-token';

  // 如果用戶未登錄且試圖訪問 /admin,重定向到 /login
  if (request.nextUrl.pathname.startsWith('/admin') && !isLoggedIn) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 可以修改響應(yīng)頭
  const response = NextResponse.next();
  response.headers.set('x-custom-header', 'my-value');
  return response;
}

// 配置中間件匹配的路徑
export const config = {
  matcher: '/admin/:path*', // 只為 /admin 下的路徑運(yùn)行中間件
};

4.2 圖像優(yōu)化 (Next.js Image Component)

Next.js 提供了一個(gè)強(qiáng)大的 <Image> 組件,用于自動(dòng)優(yōu)化圖片。

優(yōu)勢(shì):

  • 自動(dòng)優(yōu)化: 提供 WebP、AVIF 等現(xiàn)代格式。

  • 懶加載: 圖片僅在進(jìn)入視口時(shí)加載。

  • 防止布局偏移: 自動(dòng)設(shè)置寬度和高度。

  • 響應(yīng)式: 配合 sizes 屬性,提供響應(yīng)式圖片。

tsx

import Image from 'next/image';

export function MyComponent() {
  return (
    <div>
      {/* 本地圖片 */}
      <Image
        src="/me.png" // 位于 public/ 目錄
        alt="Picture of the author"
        width={500} // 必須指定
        height={500} // 必須指定
      />
      {/* 遠(yuǎn)程圖片 - 需要在 next.config.js 中配置 domains */}
      <Image
        src="https://example.com/photo.jpg"
        alt="A remote image"
        width={500}
        height={300}
        // 其他常用屬性
        placeholder="blur" // 加載時(shí)顯示模糊占位圖
        blurDataURL="data:image/jpeg;base64,..." // 小圖的 base64
        priority // 優(yōu)先級(jí)加載 (用于 LCP 圖片)
      />
    </div>
  );
}

在 next.config.js 中配置允許的圖片域名:

javascript

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ['example.com', 'images.unsplash.com'],
  },
};

module.exports = nextConfig;

4.3 靜態(tài)資源與元數(shù)據(jù) (Metadata)

在 App Router 中,你可以通過導(dǎo)出 metadata 對(duì)象來定義頁(yè)面的 SEO 信息。

tsx

// app/blog/[id]/page.tsx (更新)
import { Metadata } from 'next';

// 動(dòng)態(tài)生成元數(shù)據(jù)
export async function generateMetadata({ params }: BlogPostProps): Promise<Metadata> {
  const post = getPostById(params.id);

  if (!post) {
    return {
      title: 'Post Not Found',
    };
  }

  return {
    title: post.title,
    description: post.content.slice(0, 160), // 用內(nèi)容前160字符做描述
    openGraph: {
      title: post.title,
      description: post.content.slice(0, 160),
      type: 'article',
      publishedTime: post.date,
    },
  };
}

// ... 原有的 page 組件代碼

4.4 性能優(yōu)化

  1. 使用 next/dynamic 進(jìn)行動(dòng)態(tài)導(dǎo)入(懶加載):
    將非關(guān)鍵的組件拆分成獨(dú)立的 chunk,在需要時(shí)再加載。

    tsx

    // 動(dòng)態(tài)導(dǎo)入一個(gè)重量的組件,比如一個(gè)圖表庫(kù)
    import dynamic from 'next/dynamic';
    
    const HeavyChart = dynamic(() => import('@/app/components/HeavyChart'), {
      loading: () => <p>Loading Chart...</p>,
      ssr: false, // 如果組件只在客戶端運(yùn)行,可以禁用 SSR
    });
    
    export function Dashboard() {
      return (
        <div>
          <h1>Dashboard</h1>
          {/* 這個(gè)組件只有在渲染時(shí)才會(huì)被加載 */}
          <HeavyChart />
        </div>
      );
    }
  2. 分析包大小:
    Next.js 內(nèi)置了分析工具。運(yùn)行 npm run build 后,你可以看到每個(gè)頁(yè)面和依賴的分解。

    bash

    npm run build
    # 輸出中會(huì)包含頁(yè)面大小信息

    你也可以使用 @next/bundle-analyzer 來生成可視化的報(bào)告。

  3. 優(yōu)化字體:
    Next.js 對(duì) Google Fonts 和本地字體有很好的優(yōu)化支持,能自動(dòng)處理子集和緩存。

    tsx

    // 在布局或頁(yè)面中
    import { Inter } from 'next/font/google';
    
    const inter = Inter({ subsets: ['latin'] });
    
    export default function RootLayout({ children }) {
      return (
        <html lang="en" className={inter.className}>
          <body>{children}</body>
        </html>
      );
    }

4.5 部署

Next.js 應(yīng)用可以部署到任何支持 Node.js 的服務(wù)器,但最絲滑的體驗(yàn)是部署到 Vercel(Next.js 的創(chuàng)建者)。

部署到 Vercel:

  1. 將你的代碼推送到 GitHub、GitLab 或 Bitbucket。

  2. 在 Vercel 上注冊(cè)并連接你的 Git 倉(cāng)庫(kù)。

  3. Vercel 會(huì)自動(dòng)檢測(cè)到是 Next.js 項(xiàng)目,并配置好構(gòu)建和部署設(shè)置。

  4. 點(diǎn)擊部署。之后,每次向主分支推送代碼,都會(huì)觸發(fā)自動(dòng)部署。

其他部署平臺(tái):

  • Netlify: 同樣提供優(yōu)秀的體驗(yàn)。

  • AWS / GCP / Azure: 可以使用 Docker 或平臺(tái)特定的方式部署。

  • 你自己的服務(wù)器: 運(yùn)行 npm run build 然后 npm start。

總結(jié)與展望

恭喜你!通過這篇萬字長(zhǎng)文,你已經(jīng)系統(tǒng)地學(xué)習(xí)了 Next.js 的核心概念和高級(jí)特性。

我們從這里出發(fā):

  • 理解了 Next.js 的優(yōu)勢(shì)和項(xiàng)目創(chuàng)建。

  • 掌握了 App Router、Server/Client Components、路由、布局和數(shù)據(jù)獲取。

  • 實(shí)戰(zhàn)構(gòu)建了一個(gè)具備 CRUD 功能的全棧博客。

  • 探索了中間件、圖像優(yōu)化、元數(shù)據(jù)和性能優(yōu)化等生產(chǎn)級(jí)特性。

下一步學(xué)習(xí)方向:

  1. 狀態(tài)管理: 在復(fù)雜的 Client Components 中,考慮使用 Zustand、Jotai 或 Redux Toolkit。

  2. 測(cè)試: 學(xué)習(xí)使用 Jest 和 React Testing Library 為你的組件和頁(yè)面編寫單元測(cè)試和集成測(cè)試。

  3. 數(shù)據(jù)庫(kù)集成: 深入學(xué)習(xí)如何使用 Prisma、Drizzle ORM 或直接使用數(shù)據(jù)庫(kù)驅(qū)動(dòng)(如 pg for PostgreSQL)來持久化數(shù)據(jù)。

  4. 認(rèn)證與授權(quán): 集成 NextAuth.js 或 Auth.js 來處理復(fù)雜的用戶登錄和權(quán)限。

  5. 持續(xù)學(xué)習(xí): Next.js 生態(tài)在快速發(fā)展,關(guān)注官方博客和文檔,了解最新的特性和最佳實(shí)踐。

到此這篇關(guān)于Next.js從入門到精通的文章就介紹到這了,更多相關(guān)Next.js從入門到精通內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

抚松县| 内丘县| 濉溪县| 临潭县| 汽车| 行唐县| 虎林市| 尉氏县| 田东县| 句容市| 察雅县| 南通市| 隆昌县| 安阳县| 河北区| 崇明县| 嘉兴市| 孝昌县| 大洼县| 称多县| 霍山县| 西畴县| 延寿县| 三穗县| 张掖市| 辽源市| 襄城县| 上饶市| 宁国市| 淮南市| 鞍山市| 略阳县| 锦州市| 平利县| 北海市| 平昌县| 泉州市| 基隆市| 中江县| 宁德市| 水城县|