Skip to content
第 20 章 前端 ⏱ 12 分钟阅读

第 20 章:Next.js 入门 ​

学习目标 ​

  • 理解 Next.js 解决了什么问题
  • 创建 Next.js 14 项目(App Router)
  • 区分 Server Components vs Client Components
  • 避开 Next.js 的 4 个常见坑

一、Next.js 是什么? ​

Next.js 是基于 React 的全栈框架,提供:

  • 文件系统路由
  • SSR / SSG / ISR
  • API Routes
  • 优化(图片、字体、Script)
  • 部署到 Vercel 一键上线

二、为什么用 Next.js 14? ​

维度Vite + ReactNext.js
路由手动配文件系统
SSR额外配置内置
SEO弱强
部署任意静态Vercel 一键
Server Components❌✅

三、创建项目 ​

bash
npx create-next-app@latest my-app
# 选项:
# ✅ TypeScript
# ✅ ESLint
# ✅ Tailwind CSS
# ✅ src/ 目录
# ✅ App Router(推荐)
# ❌ import alias 默认

cd my-app
npm run dev

访问 http://localhost:3000。

⚠️ 坑 1:不用 --typescript、--tailwind 等参数会被询问;用 --yes 全默认。

四、目录结构(App Router) ​

my-app/
├── src/
│   ├── app/
│   │   ├── layout.tsx       # �️ 根布局
│   │   ├── page.tsx         # / 路由
│   │   ├── globals.css
│   │   └── about/
│   │       └── page.tsx     # /about 路由
│   └── components/
├── public/                   # 静态资源
├── next.config.js
├── tailwind.config.ts
└── tsconfig.json

五、第一个页面 ​

src/app/page.tsx:

tsx
export default function Home() {
  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold">Hello, Next.js!</h1>
      <p>当前时间:{new Date().toLocaleString()}</p>
    </main>
  );
}

⚠️ 坑 2:App Router 默认Server Component(page.tsx 无 "use client"),不能直接用 useState 等 hooks。

Server Component 不会跨域——因为 fetch 是服务端 Node.js 发请求,不是浏览器,没有 CORS:

tsx
// ✅ Server Component:服务端 fetch,无 CORS 限制
async function Page() {
  const r = await fetch('http://localhost:8080/secret') // ✅ 内网也行
  return <div>{r.data}</div>
}

"不能用 await"是误读——两种组件都能 await fetch,差别在写法位置:

tsx
// ① Server Component:顶层直接 await(函数本身要 async)
async function Page() {
  const data = await fetch('/api/users')  // ✅ OK
  return <div>{data.name}</div>
}

// ② Client Component:await 必须包在 useEffect 或事件里
'use client'
function Page() {
  const [data, setData] = useState(null)
  useEffect(() => {
    async function load() {
      const res = await fetch('/api/users')  // ✅ OK(也能 await)
      setData(await res.json())
    }
    load()
  }, [])
  return <div>{data?.name}</div>
}

"后端能力" ≠ 写后端接口——Server Component 的"后端能力"就三件事:

能力例子
① 调内部 API(无 CORS)fetch('http://内网/secret')
② 读文件 / 读数据库await db.query() / import data from './x.json'
③ 用 API Key 等机密process.env.SECRET_KEY(不进浏览器)

写接口在 app/api/xxx/route.ts,不在 page.tsx。

💡 Server Component 不是写后端,是"前端代码能直接用后端资源"——调内部 API / 读文件 / 用 Key 都不暴露给浏览器。Vite + React 想要这些能力,得自己搭后端 + 处理 CORS + 保护 API Key。

六、Server vs Client Components ​

Server Component(默认) ​

  • 在服务器渲染,不发到浏览器
  • 可以直接 await fetch()
  • 不能用 hooks、事件、浏览器 API
  • bundle 体积更小,SEO 更好
tsx
// page.tsx(默认)
async function getData() {
  const r = await fetch('https://api.example.com/data');
  return r.json();
}

export default async function Page() {
  const data = await getData();     // ⚠️ 顶层 await
  return <div>{data.title}</div>;
}

Client Component(交互用) ​

  • 加 "use client" 指令
  • 可以用 hooks、事件
  • 在浏览器运行
tsx
'use client';

import { useState } from 'react';

export default function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

⚠️ 坑 3:"use client" 是边界标识,该组件及其 children 都成 Client Component,树最底层加最划算。

七、嵌套 Server + Client ​

tsx
// app/page.tsx(Server)
import Counter from './Counter';

export default async function Page() {
  const data = await fetch('...').then(r => r.json());
  return (
    <main>
      <h1>{data.title}</h1>
      <Counter />        {/* 客户端组件,嵌入服务端树 */}
    </main>
  );
}

八、布局(layout.tsx) ​

tsx
// app/layout.tsx(必须有)
import './globals.css';

export const metadata = {
  title: 'My App',
  description: 'Next.js 14 教程',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>{children}</body>
    </html>
  );
}

九、字体与图片优化 ​

tsx
import { Inter } from 'next/font/google';        // ① Google 字体加载器(避免 FOIT 闪烁)
import Image from 'next/image';                  // ② Next.js 优化版 <img>(懒加载 + WebP)

const inter = Inter({ subsets: ['latin'] });     // ③ 加载 Inter 字体,只下 latin 子集(体积小)

export default function Page() {
  return (
    <main className={inter.className}>           // ④ 给 main 加 className,继承字体样式
      <Image
        src="/hero.png"                          // 图片路径(public 目录下)
        alt="hero"                               // 必填!无障碍 + SEO
        width={800}                              // 必须传宽高(布局稳定,不抖动)
        height={400}
        priority                                 // 关键图:不懒加载,首屏优先加载
      />
    </main>
  );
}

next/font 的好处:

  • 自动自托管字体(build 时下载,运行时零请求 Google)
  • 消除 FOIT(字体加载时的闪烁)
  • 按 subsets 切片,只下需要的字符(体积小)

next/image 的好处(vs 普通 <img>):

  • 自动懒加载(滚到才加载)
  • 自动转 WebP(体积小)
  • 响应式 srcset(不同屏幕不同尺寸)
  • 占满宽度的图:fill + 父元素 position: relative

⚠️ 坑 4:不用 next/image 直接用 <img> 不会自动优化(懒加载、WebP)。

十、metadata SEO ​

tsx
// 静态 metadata(写死)
export const metadata = {
  title: '关于我',                              // 页面标题(<title>)
  description: '我的个人介绍',                   // 描述(<meta name="description">)
};

// 动态 metadata(根据参数生成,如详情页)
export async function generateMetadata(
  { params }: { params: { id: string } }        // 解构路由参数(动态路由 [id])
) {
  return {
    title: `用户 ${params.id}`,                 // /user/123 → 标题"用户 123"
    description: `这是用户 ${params.id} 的主页`,
  };
}

两种用法区别:

写法适用
export const metadata = {...}静态页面(关于我、首页)
export async function generateMetadata({ params })动态页面(详情页 /user/[id])

Next.js 自动做的事:

  • 把 title 塞到 <title> 标签
  • 把 description 塞到 <meta name="description">
  • 还能配 openGraph(分享卡片)、twitter、icons 等
tsx
export const metadata = {
  title: '关于我',
  description: '我的个人介绍',
  openGraph: {
    title: '关于我',
    description: '分享时的描述',
    images: ['/og.png'],           // 分享时显示的图
  },
}

对比传统 HTML:

html
<!-- 传统写法:每个页面写一遍 -->
<head>
  <title>关于我</title>
  <meta name="description" content="我的个人介绍">
</head>

<!-- Next.js:在 page.tsx 里 export 一个对象就完事 -->

十一、本章小结 ​

要点关键
创建create-next-app@latest
App Routerapp/ 目录即路由
默认类型Server Component
Client文件顶部加 "use client"
边界客户端边界放树最底层
优势SSR/SEO/优化/Vercel 部署
优化next/image、next/font

动手练习 ​

  1. 初始化:创建 my-first-next,跑通默认页
  2. 双页面:/ 显示标题,/about 显示介绍,加导航 <Link>
  3. 客户端组件:在 /about 加一个 Counter 组件(交互)
  4. 找坑:以下代码运行报什么错?
    tsx
    export default function Page() {
      const [n, setN] = useState(0);
      return <div>{n}</div>;
    }

下一章:第 21 章:Next.js 路由 →

本站基于 VitePress 构建 · 由 StackHub 团队维护