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

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

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

引言:為什么是 Next.js?

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

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

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

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

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

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

  4. API 路由: 在同一個項目中編寫前端和后端 API。

  5. 出色的開發(fā)者體驗: 快速刷新、強大的錯誤提示等。

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

本教程將分為四個部分:

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

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

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

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

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

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

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

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

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

bash

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

你會被提示選擇一些配置項:

  • 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,保持項目結(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),功能更強大,是未來的方向。

等待依賴安裝完成后,進入項目目錄并啟動開發(fā)服務(wù)器:

bash

cd my-nextjs-app
npm run dev

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

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

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

text

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

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

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

  • app/page.tsx: 這是你的首頁。當你訪問 / 時,渲染的就是這個組件。

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

1.3 理解 React Server Components vs Client Components

這是 App Router 的核心概念。

  • Server Components (默認):

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

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

    • 有助于減少客戶端包大小。 因為代碼在服務(wù)端執(zhí)行。

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

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

  • Client Components:

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

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

    • 可以處理交互性。 如點擊事件、表單提交。

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

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

tsx

// app/page.tsx
// 這是一個 Server Component (默認)

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

// 一個普通的 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 />
      {/* 我們引入一個 Client Component */}
      <ClientCounter />
    </div>
  );
}

// --- 新建一個 Client Component ---
// 在 app/components/ClientCounter.tsx
"use client"; // <-- 必須的指令,標記這是一個 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>
  );
}

在這個例子中:

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

  • ClientCounter 因為有了 "use client",它是一個 Client Component,其代碼會被打包發(fā)送到瀏覽器,處理點擊事件和狀態(tài)更新。

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

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

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

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

基礎(chǔ)路由:

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

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

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

在 page.tsx 中,你可以通過 params 屬性訪問到這個動態(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,它在多個頁面之間保持狀態(tài),并且不會在路由切換時重新渲染。它由 layout.tsx 文件定義。

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

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

tsx

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {/* 一個所有頁面共享的導(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>
        {/* 子頁面將在這里被渲染 */}
        <main className="container mx-auto p-4">{children}</main>
        {/* 一個所有頁面共享的頁腳 */}
        <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 中擴展了 fetch API,提供了強大的數(shù)據(jù)獲取能力,包括自動緩存和重復(fù)數(shù)據(jù)刪除。

三種渲染模式:

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

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

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

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

tsx

// app/blog/page.tsx
// 這是一個 Server Component

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

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

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

// ISR (Incremental Static Regeneration)
// 靜態(tài)生成,但可以定期在后臺重新驗證和更新
async function getIncrementalPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    next: { revalidate: 60 }, // <-- 每60秒重新驗證一次
  });
  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)入,會應(yīng)用到所有頁面。

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

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

  • Styled-components / Emotion: CSS-in-JS 庫(通常需要在 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 端點,這意味著你可以在同一個項目中編寫全棧應(yīng)用。

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

創(chuàng)建一個簡單的 GET API:

tsx

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

現(xiàn)在,訪問 http://localhost:3000/api/hello 將會返回 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ù)庫
  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";
// ... 在某個 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);
};

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

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

  • 博客文章列表(SSG)

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

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

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

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

創(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(), // 簡單的 ID 生成
  };
  posts.push(newPost);
  return newPost;
}

3.2 博客列表頁面 (SSG)

tsx

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

// 這個頁面將在構(gòu)建時靜態(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 博客文章詳情頁面 (動態(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)建時預(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 頁面
  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 端點

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;

    // 簡單的驗證
    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)建一個表單來發(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>
  );
}

然后,在博客列表頁面引入這個表單:

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)

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

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

tsx

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

export function middleware(request: NextRequest) {
  // 檢查 cookie 或 header 來進行簡單的認證
  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 下的路徑運行中間件
};

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

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

優(yōu)勢:

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

  • 懶加載: 圖片僅在進入視口時加載。

  • 防止布局偏移: 自動設(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} // 必須指定
      />
      {/* 遠程圖片 - 需要在 next.config.js 中配置 domains */}
      <Image
        src="https://example.com/photo.jpg"
        alt="A remote image"
        width={500}
        height={300}
        // 其他常用屬性
        placeholder="blur" // 加載時顯示模糊占位圖
        blurDataURL="data:image/jpeg;base64,..." // 小圖的 base64
        priority // 優(yōu)先級加載 (用于 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 對象來定義頁面的 SEO 信息。

tsx

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

// 動態(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 進行動態(tài)導(dǎo)入(懶加載):
    將非關(guān)鍵的組件拆分成獨立的 chunk,在需要時再加載。

    tsx

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

    bash

    npm run build
    # 輸出中會包含頁面大小信息

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

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

    tsx

    // 在布局或頁面中
    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ù)器,但最絲滑的體驗是部署到 Vercel(Next.js 的創(chuàng)建者)。

部署到 Vercel:

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

  2. 在 Vercel 上注冊并連接你的 Git 倉庫。

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

  4. 點擊部署。之后,每次向主分支推送代碼,都會觸發(fā)自動部署。

其他部署平臺:

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

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

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

總結(jié)與展望

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

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

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

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

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

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

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

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

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

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

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

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

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

相關(guān)文章

最新評論

秀山| 盘山县| 静乐县| 法库县| 东源县| 营口市| 灌南县| 镇平县| 陕西省| 五大连池市| 从化市| 鸡西市| 太原市| 吉安市| 留坝县| 大宁县| 鸡东县| 扎鲁特旗| 改则县| 涞源县| 台东县| 松江区| 大城县| 博野县| 视频| 饶河县| 抚远县| 丹阳市| 鄢陵县| 南丹县| 乐清市| 汽车| 偃师市| 东城区| 阿坝| 沅陵县| 新泰市| 于田县| 黔西县| 梅州市| 金堂县|