Next.js 入门实战:从 SPA 到全栈,一次搞懂

Next.js 入门实战:从 SPA 到全栈,一次搞懂

React 写 SPA 很高效,但当页面需要搜索收录、首屏内容、服务端数据和轻量接口时,单独组合路由、数据请求和服务端能力会越来越重。

Next.js 不是"替代 React 的新框架",而是给 React 加上了路由、服务端渲染、数据获取和后端入口的一套完整约定。你仍然写 React 组件,只是有了更多运行位置可以选择。

本文以 App Router 为主线,讲清楚文件路由、Layout、Server/Client Component、Route Handler 和一个 Todo 数据流。

一、SPA 的问题不只是 SEO

传统 SPA 常见的首屏流程是:浏览器先拿到基础 HTML,再下载 JavaScript,最后在浏览器请求数据并渲染页面。

text 复制代码
HTML -> 下载 JS -> 执行 JS -> 请求数据 -> 渲染内容

这会带来三个挑战:

  • 首屏内容依赖 JavaScript、网络和客户端渲染
  • 搜索引擎与社交分享抓取对 JavaScript 的执行能力不完全一致
  • 页面和接口通常需要分别部署、分别维护

但"SPA 的 HTML 一定只有空 div,SEO 一定很差"并不准确。现代搜索引擎可以执行部分 JavaScript,SPA 也能做好 SEO;只是对于内容型页面,服务端先提供完整内容通常更稳定、首屏更快。

Next.js 的核心价值是提供多种渲染和数据获取方式,而不是让所有页面都强制 SSR。

二、Next.js 是什么?

Next.js 是 React 的全栈框架。它提供文件路由、服务端组件、渲染策略、Route Handler 和生产构建能力。

能力 在项目中解决什么
App Router 用文件和文件夹定义页面路由
Server Component 在服务端读取数据并生成 UI 结果
Client Component 承载状态、事件和浏览器 API
Layout 多级页面共享导航、侧栏和结构
Route Handler 在同一仓库提供 HTTP 接口
Metadata 声明标题、描述等页面元数据

创建项目:

bash 复制代码
npx create-next-app@latest my-next-app
cd my-next-app
npm run dev

创建器会提供 TypeScript、ESLint、App Router 等选项。Tailwind CSS 是常见选择,不是 Next.js 运行的必需条件。

三、App Router:文件即路由

App Router 的约定很直接:文件夹定义 URL 片段,page.tsx 定义页面,layout.tsx 定义共享外壳。

text 复制代码
app/
├── layout.tsx                 # 根布局
├── page.tsx                   # /
├── about/
│   └── page.tsx               # /about
├── dashboard/
│   ├── layout.tsx             # /dashboard 下的共享布局
│   ├── page.tsx               # /dashboard
│   └── settings/page.tsx      # /dashboard/settings
├── todos/page.tsx             # /todos
└── api/todos/route.ts         # /api/todos

这不需要手写 <Routes>。当用户访问 /dashboard/settings,Next.js 会匹配目录中的 page.tsx,并自动嵌套沿途的 Layout。

四、Layout:共享结构,不是每页复制导航

根布局是必须存在的页面骨架,通常放全局样式、字体和主导航:

tsx 复制代码
// app/layout.tsx
import Link from "next/link";
import type { ReactNode } from "react";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>
        <nav><Link href="/">首页</Link> <Link href="/todos">Todos</Link></nav>
        {children}
      </body>
    </html>
  );
}

children 是当前匹配页面的插槽。访问 /dashboard/settings 时,渲染层级可以理解为:

text 复制代码
RootLayout
  └── DashboardLayout
        └── SettingsPage

布局会在路由切换中被复用,但不要把它简单理解成"外层永远不重新渲染"。实际行为还会受到导航方式、缓存、数据依赖和客户端状态影响;它的工程价值是把共享 UI 和页面内容按路由层级组织起来。

五、Server Component 和 Client Component

App Router 中,组件默认是 Server Component。它们可以在服务端读取数据库、调用内部服务、使用密钥环境变量,但不能使用 useState、事件处理函数或浏览器 API。

tsx 复制代码
// app/about/page.tsx
export default function AboutPage() {
  return <h1>关于我们</h1>;
}

需要交互时,在文件顶部写 "use client"

tsx 复制代码
"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((value) => value + 1)}>{count}</button>;
}

关键理解:"use client" 表示该模块及其依赖进入客户端 JavaScript 边界,不等于它只会在浏览器生成 HTML。Next.js 仍可能先为初始页面生成 HTML,再由浏览器下载 JavaScript 并水合,使事件能够工作。

选型很简单:

需求 优先方案
读取服务端数据、展示内容 Server Component
点击、输入、状态、浏览器 API Client Component
既要首屏数据又要交互 Server Page 获取数据,传给小型 Client Component

不要把整页都标成 "use client"。把交互边界缩小到真正需要状态和事件的组件,通常能减少浏览器需要下载的 JavaScript。

六、Route Handler:同仓库提供 HTTP 接口

app/api 下创建 route.ts,导出的 HTTP 方法就是接口入口:

ts 复制代码
// app/api/todos/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  const todos = await todoRepository.list();
  return NextResponse.json(todos);
}

GETPOSTPUTPATCHDELETE 等导出函数分别对应 HTTP 方法。Route Handler 适合 BFF、Webhook、表单提交或给客户端组件提供接口。

不过它不是"免部署的完整后端"。数据库、认证、权限、限流、日志和持久化仍然要按后端标准设计。

特别注意:下面这种内存数组只适合教程演示:

ts 复制代码
const todos = [];

在开发热更新、无服务器实例扩缩容和多进程环境中,它不会可靠持久化。生产数据应存进数据库或外部持久化服务。

七、Todo 实战:服务端拿初始数据,客户端做交互

页面组件可以直接在服务端获取初始列表:

tsx 复制代码
// app/todos/page.tsx
import { TodoList } from "./todo-list";

export default async function TodosPage() {
  const todos = await todoRepository.list();
  return <TodoList initialTodos={todos} />;
}

列表组件只处理用户交互:

tsx 复制代码
// app/todos/todo-list.tsx
"use client";

import { useState } from "react";

export function TodoList({ initialTodos }) {
  const [todos, setTodos] = useState(initialTodos);

  async function addTodo(content) {
    const response = await fetch("/api/todos", {
      method: "POST",
      body: JSON.stringify({ content }),
    });
    const todo = await response.json();
    setTodos((items) => [...items, todo]);
  }
}

这个结构有两个好处:首次列表数据不必等浏览器 useEffect 再请求一次;浏览器只下载真正需要交互的 TodoList 代码。

正式项目还需要处理请求失败、加载状态、输入校验和接口认证。客户端提交成功后,也可以使用 router.refresh() 重新获取服务端页面数据;选择乐观更新还是刷新数据,取决于交互复杂度和一致性要求。

八、SEO:先把内容、元数据和可访问性做好

服务端渲染是 SEO 的有利条件,但不是 SEO 的全部。一个更可靠的优先级是:

  1. 有价值、可索引的页面内容
  2. 正确的标题、描述和分享元数据
  3. 可访问的语义化 HTML、稳定 URL 和内部链接
  4. 合适的数据获取与渲染策略

页面元数据可以通过 metadata 导出声明:

tsx 复制代码
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "我的 Todo 应用",
  description: "使用 Next.js 构建的待办事项应用",
};

不要把"Client Component"直接等同于 SEO 差。真正要问的是:用户和爬虫在首个可访问响应中能拿到什么内容?页面是否有正确元数据、链接与内容结构?

九、常见误区

1. Next.js 等于 SSR

不完全正确。Next.js 可以按页面和数据选择不同策略,包括静态生成、动态服务端渲染和客户端交互。它提供的是选择权,而不是单一渲染模式。

2. use client 后,组件只在浏览器运行

不正确。"use client" 定义客户端边界,组件需要客户端 JavaScript 才能交互;初始 HTML 仍可能由服务端预渲染。

3. 有了 Route Handler 就不需要后端工程

不正确。Route Handler 解决的是接口入口位置,不会自动解决数据库、鉴权、缓存、可观测性和安全问题。

十、总结

概念 一句话理解
App Router 用目录和约定组织页面、布局和接口
page.tsx 当前路由的页面内容
layout.tsx 当前路由及后代的共享结构
Server Component 默认服务端组件,适合数据读取和内容展示
Client Component "use client",适合交互和浏览器 API
Route Handler route.ts 中导出 HTTP 方法处理接口
Metadata 声明页面标题、描述等 SEO 基础信息

Next.js 的核心不是"把 React 搬到服务器",而是把路由、服务端数据、客户端交互和接口边界组织在同一套框架约定中。理解 Server/Client Component 的边界后,SPA 与全栈之间不再是二选一,而是根据页面目标选择合适的组合。

相关推荐
烬羽2 小时前
彻底搞懂 'use client':Next.js 组件根本不是"只在浏览器渲染"
全栈·next.js·前端工程化
用户9385156350715 小时前
深入理解 Next.js:从 SPA 的 SEO 问题到约定式路由与 SSR 原理
前端·后端·全栈
程序员黑豆1 天前
Java 内存存储机制:基本数据类型 vs 引用数据类型
前端·ai编程·全栈
用户938515635072 天前
从前后端分离到前端接口工程:React + MockJS + Vite 解析
前端·后端·全栈
程序员黑豆2 天前
深入解析Java数据类型:基本类型与引用类型的本质区别与实战选择
前端·ai编程·全栈
前端双越老师2 天前
前端学习 Java 其实很容易:TS 和 Java 语法的 N 个相同点
java·全栈
烬羽3 天前
navigator.gpu 存在就够了?WebGPU 检测的两层陷阱
typescript·浏览器·全栈
Hamm4 天前
前端转Java+AI全栈?那我推荐几个实战开源项目给你
前端·后端·全栈
三雒4 天前
拆解 Codex:一个桌面 Agent 到底由哪些核心部分组成?
agent·全栈