前言
这两年,前端技术依然日新月异,成熟的项目难免版本老旧,面对客观情况,相比代码洁癖、追求前沿,一句“能跑就行”往往更胜一筹。最近在维护一个 Next.js 项目,一看版本 13.x,那就巩固一下吧,下面是要点提炼。
Next.js 目录结构
_app.tsx用于自定义每个页面的根组件。你可以在这里添加全局样式、布局、页面切换逻辑、全局状态管理等。它包裹了所有页面组件。_document.tsx用于自定义整个 HTML 文档结构(如<html>、<head>、<body>)。这里适合放置自定义的 meta 标签、字体链接等。只在服务端渲染时运行,不处理页面交互逻辑。pages目录下的文件会自动成为路由。文件名对应路由路径,文件夹对应嵌套路由。public目录下的文件会被 Next.js 直接复制到构建输出目录。可以放置静态资源,如图片、字体等。next.config.js是 Next.js 的配置文件,可以在这里配置一些全局的设置,如环境变量、路由重写、国际化等。
脚本
-
npm run build:运行 build 脚本一次,该脚本将在 .next 文件夹中构建生产应用程序。 -
npm run start:构建完成后, start 脚本将启动一个支持混合页面的 Node.js 服务器,该服务器提供静态生成和服务器端渲染的页面以及 API 路由。- Tip:可以在 package.json 中自定义 start 脚本以接受 PORT 参数:
"start": "next start -p $PORT"
- Tip:可以在 package.json 中自定义 start 脚本以接受 PORT 参数:
pre-rendering
默认情况下,Next.js 会对每个页面进行预渲染。这意味着 Next.js 会提前为每个页面生成 HTML,而不是完全依赖客户端 JavaScript 来完成。预渲染可以带来更好的性能和 SEO 优化。
每个生成的 HTML 都关联着该页面所需的最少量 JavaScript 代码。当浏览器加载页面时,对应的 JavaScript 代码会执行,使页面完全可交互(这个过程称为"水合 hydration")。
Next.js 支持两种预渲染形式:
- 静态生成(Static Generation):在构建时生成 HTML,并在每次请求时复用(推荐方式)
- 服务端渲染(Server-side Rendering):在每次请求时生成 HTML
(注:hydration 在中文前端领域通常译为"水合"或" hydration 渲染",指将静态 HTML 与客户端 JavaScript 状态结合的过程)
两种方式:Static Generation / Server-side Rendering
- Static Generation:在构建时(
next build时)生成 HTML,适合静态内容(如博客、文档、marketing pages、E-commerce product listings 等)。可以使用getStaticProps和getStaticPaths。
- Server-side Rendering:在每次请求时生成 HTML,适合动态内容(如用户数据、实时数据等)。可以使用
getServerSideProps。
pre-rendering vs no pre-rendering
- 预加载/提前加载
- 不提前加载
Static Generation with Data
在构建时,一些页面需要外部数据或者不需要。
外部数据源:
- access the file system
- fetch external API
- query a database
getStaticProps:用于静态生成页面的数据获取。它在构建时运行,返回的数据会被序列化成 JSON,并作为 props 传递给页面组件。可以缓存在 CDN。开发环境下,每一次请求都会执行;生产环境下,只有在构建时执行。
由于 getStaticProps 只在构建时运行,意味着它不会运行在客户端,也就是说它不会被打包到浏览器中。因此,写代码时可以写数据库连接字符串、API 密钥等敏感信息,而不必担心泄露给浏览器。同时,将无法使用仅在请求时间才可用的数据,例如查询参数或 HTTP 头信息。
export default function Home(props) { ... }
export async function getStaticProps() {
// Get external data from the file system, API, DB, etc.
const data = ...
// The value of the `props` key will be
// passed to the `Home` component
return {
props: ...
}
}
Server-side Rendering with Data
getServerSideProps:用于服务端渲染页面的数据获取。它在每次请求时运行,返回的数据会被序列化成 JSON,并作为 props 传递给页面组件。
export default function Home(props) { ... }
export async function getServerSideProps(context) {
// Get external data from the file system, API, DB, etc.
const data = ...
// The value of the `props` key will be
// passed to the `Home` component
return {
props: {
// props for your component
},
}
}
context 参数包含与当前请求相关的信息,常用属性有:
params:动态路由参数(如 [id].js 中的 id)。req:Node.js 的 HTTP 请求对象(IncomingMessage)。res:Node.js 的 HTTP 响应对象(ServerResponse)。query:URL 查询参数对象。resolvedUrl:请求的实际 URL(包含查询参数)。locale、locales、defaultLocale:与国际化相关的信息(如启用 i18n 时)。
getServerSideProps:用于服务端渲染页面的数据获取。它在每次请求时运行,返回的数据会被序列化成 JSON,并作为 props 传递给页面组件。
export default function Home(props) { ... }
export async function getServerSideProps(context) {
// Get external data from the file system, API, DB, etc.
const data = ...
// The value of the `props` key will be
// passed to the `Home` component
return {
props: {
// props for your component
},
}
}
context 参数包含与当前请求相关的信息,常用属性有:
params:动态路由参数(如 [id].js 中的 id)。req:Node.js 的 HTTP 请求对象(IncomingMessage)。res:Node.js 的 HTTP 响应对象(ServerResponse)。query:URL 查询参数对象。resolvedUrl:请求的实际 URL(包含查询参数)。locale、locales、defaultLocale:与国际化相关的信息(如启用 i18n 时)。
Client-side Rendering(no pre-rendering)
- 静态生成(预渲染) 页面中不需要外部数据的部分
- 当页面加载时,通过客户端 JavaScript 获取外部数据,并填充剩余部分
适用于频繁更新数据的页面,或者数据依赖于用户输入的页面。例如 dashboard 页面,因为它是一个私有的、特定用户的页面,这种页面不需要预渲染,每一次请求都需要获取最新的数据。
fetch data with SWR
SWR 是一个 React Hooks 库,用于数据获取和缓存。它的核心思想是:Stale-While-Revalidate,即先返回缓存的数据,然后异步请求最新的数据并更新缓存。
import useSWR from 'swr';
function Profile() {
const { data, error } = useSWR('/api/user', fetch);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
Dynamic Routes
动态路由是 Next.js 的一个强大特性,可以根据 URL 参数动态生成页面。可以通过文件名中的方括号来定义动态路由。例如,pages/posts/[id].js 可以匹配 /posts/1、/posts/2 等 URL。
getStaticPaths:用于静态生成动态路由页面时,指定哪些路径需要预渲染。它在构建时运行,返回一个包含所有需要预渲染的路径的数组。同样可以获取任何外部数据源。开发环境下,每次请求都会执行。生产环境下,只有在构建时执行。
动态路由页面必须包含:
getStaticPaths:用于指定哪些路径需要预渲染。getStaticProps:用于获取每个路径对应的数据。
import Layout from '../../components/layout';
export default function Post(props) {
return (
<Layout>
<h1>{props.data.title}</h1>
<p>{props.data.content}</p>
</Layout>
);
}
export async function getStaticPaths() {
// Return a list of possible value for id
const paths = [{ params: { id: '1' } }, { params: { id: '2' } }, { params: { id: '3' } }];
return {
paths,
fallback: false, // See the "fallback" section below
};
}
export async function getStaticProps({ params }) {
// Fetch necessary data for the blog post using params.id
const data = await fetch(`https://.../${params.id}`);
return {
props: {
data,
},
};
}
fallback: false:只有paths中返回的路径会被生成,其他路径访问会直接返回 404。fallback: true:未在paths中的路径,首次访问时会在服务端生成静态页面,之后缓存。fallback: 'blocking':和 true 类似,但用户会等待页面生成后再显示(不会看到 loading 状态)。
useRouter
useRouter 是 Next.js 提供的一个 Hook,用于获取路由信息和进行路由跳转。可以在函数组件中使用。
import { useRouter } from 'next/router';
function Post() {
const router = useRouter();
const { id } = router.query;
return <p>Post: {id}</p>;
}
API Routes
API Routes 是 Next.js 提供的一种功能,可以在 pages/api 目录下创建 API 接口。每个文件对应一个 API 路由,可以处理 GET、POST、PUT、DELETE 等请求。
// pages/api/hello.ts
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}
然后访问 /api/hello 即可获取 JSON 数据。
注意:你不应该从 getStaticProps 或 getStaticPaths 获取 API 路由。相反,你应该直接在 getStaticProps 或 getStaticPaths 中编写你的服务器端代码(或者调用一个辅助函数)。
原因如下:getStaticProps 和 getStaticPaths 仅在服务器端运行,永远不会在客户端运行。此外,这些函数不会包含在浏览器 JS 包中。这意味着你可以编写直接执行数据库查询等代码,而无需发送到浏览器。
处理表单输入
在页面上创建一个表单,然后使用 fetch API 将数据发送到 API 路由。
export default function handler(req, res) {
const email = req.body.email;
// Then save email to your database, etc...
}
Headless CMS
Headless CMS(无头内容管理系统)是一种只负责内容管理和存储、不负责内容展示的 CMS。它只提供内容的 API(通常是 REST 或 GraphQL),前端页面如何展示内容完全由开发者决定。
特点:
- 没有“头部”(即没有内置的前端模板或页面渲染)
- 内容通过 API 提供给任何平台(Web、App、小程序等)
- 前后端分离,灵活性高
常见 headless CMS:
- Contentful
- Strapi
- Sanity
- Prismic
适用场景:
- 多端内容分发
- 需要自定义前端技术栈
- 追求内容与展示彻底分离
适合用 Preview Mode 的场景:
- 需要在 CMS 中编辑内容,并希望在保存后立即预览效果。