SSG (Static Site Generation)
一种预渲染的技术,它在构建时生成页面的 HTML,而不是在每次请求时生成。这种方法可以显著提高页面的加载速度,因为生成的 HTML 页面可以直接从 CDN 缓存中获取,而不需要在每次请求时进行服务器渲染。
工作原理
- 构建时生成 HTML:
- 在运行
next build
命令时,Next.js 会根据代码生成所有需要静态生成的页面的 HTML 文件 - 这些 HTML 文件包含了所有的页面内容,因此可以在没有额外服务器处理的情况下直接提供给用户
- 运行时使用生成的 HTML:
- 在生产环境中,每次有用户请求页面时,预先生成的 HTML 文件会被直接提供给用户
- 由于这些文件已经在构建时生成,所以请求的处理速度非常快,且可以通过 CDN 缓存进一步加速
静态生成的优势
- 性能:预生成的 HTML 文件可以直接提供给用户,不需要每次请求都进行服务器渲染,因此加载速度更快。
- 可缓存:静态文件可以很容易地通过 CDN 缓存,这意味着可以显著减少服务器的负载和响应时间。
- SEO:预生成的页面包含所有的静态内容,有助于搜索引擎优化,因为搜索引擎可以轻松地抓取和索引这些页面。
场景
如果不能在用户请求之前预渲染,也就是页面需要显示经常更新的数据,并且页面内容会在每次请求时都会发生变化,那就不能使用静态生成
- 内容不频繁变化:博客文章和作品集、电子商务产品列表、营销页面、帮助和文档等
- 需要 SEO 优化:搜索引擎友好的页面。
- 性能优化:静态生成的页面加载速度更快。
- 构建时确定的数据:例如从 CMS 获取的数据,构建时即可固定。
getStaticProps
概念
用于在构建时生成静态页面。它允许你在构建时获取数据,并将其作为 props 传递给页面组件
- 构建时获取数据:
getStaticProps
在构建时执行,适用于获取不会频繁变化的数据。
- 返回对象:必须返回一个对象,包含
props
属性,传递给页面组件。
- 支持异步操作:可以在函数内部使用异步操作,如调用外部 API 或数据库。
- 增量静态生成 (ISR):通过设置
revalidate
,可以让页面在指定时间间隔后重新生成。
注意事项
- 只能在Next.js 项目的
pages
目录下的文件中使用,因为它用于静态生成页面
- 生成时间:它在构建时运行,因此适用于不频繁更新的数据。
- 数据静态化:生成的页面是静态的,适合 SEO 和性能优化。
- 动态参数:与
getStaticPaths
配合使用,可生成动态路由。
- 增量静态再生:可以通过
revalidate
参数设置页面的更新频率。
- 限制:在编译时的数据量过大可能影响构建时间。
getInitialProps
getInitialProps
可以在页面和自定义的 App 或 Document 组件中使用。它的主要功能是获取数据并在服务器端或客户端渲染页面时传递这些数据。使用场景
- 页面级别的数据获取: 如果你的页面需要在每次请求时从外部 API 获取数据,例如博客文章列表或用户信息,可以使用
getInitialProps
。
- 自定义 App 或 Document: 如果你需要在整个应用中进行一些初始化操作,例如用户身份验证状态检查或全局数据获取,可以在
_app.js
中使用getInitialProps
注意事项
- 客户端和服务器端运行:
getInitialProps
在首次页面加载时会在服务器端运行,在客户端导航时会在客户端运行。需要注意代码的执行环境,避免在服务器端使用只能在客户端使用的 API。
- 性能影响: 由于
getInitialProps
在每次页面请求时都会执行,这可能会影响性能,特别是在客户端导航时。相比之下,getStaticProps
和getServerSideProps
分别用于构建时和请求时获取数据,可以更好地控制性能。
- 不能与
getStaticProps
或getServerSideProps
一起使用: 你不能在同一个页面中同时使用getInitialProps
和getStaticProps
或getServerSideProps
。
generateStaticParams
用于在构建时预生成所有静态页面的参数。这个函数在 Notion 的某些应用中使用,用来生成特定页面的静态路径及其参数。
例子
import { ExtendedRecordMap } from 'notion-types'; import { getAllPagesInSpace, parsePageId, uuidToId } from 'notion-utils'; import { getNotionPageData } from './notion-api'; // 假设这是获取 Notion 页面数据的函数 /** * 获取所有静态页面的路径参数。 * @returns {Promise<Array<{ params: { id: string } }>>} 包含页面 ID 的路径参数数组 */ export async function generateStaticParams(): Promise<Array<{ params: { id: string } }>> { // 获取 Notion 空间中的所有页面 const rootNotionPageId = 'YOUR_ROOT_NOTION_PAGE_ID'; const rootNotionSpaceId = 'YOUR_ROOT_NOTION_SPACE_ID'; // 使用 Notion API 获取空间中的所有页面 const pages = await getAllPagesInSpace(rootNotionPageId, rootNotionSpaceId, getNotionPageData); // 将页面 ID 解析为符合 UUID 格式的 ID,并生成路径参数数组 return Object.keys(pages).map((pageId) => { const cleanPageId = parsePageId(pageId, { uuid: false }); return { params: { id: cleanPageId || uuidToId(pageId), }, }; }); } /** * 获取 Notion 页面的数据。 * @param {string} pageId 页面 ID * @returns {Promise<ExtendedRecordMap>} Notion 扩展记录映射 */ async function getNotionPageData(pageId: string): Promise<ExtendedRecordMap> { // 假设这是获取 Notion 页面数据的函数,可以调用 Notion API 获取页面内容 // 这里仅作示例,实际实现需要根据你的 API 逻辑编写 return {} as ExtendedRecordMap; }
路由
通过在
pages
目录下创建文件,Next.js 可以自动根据文件和目录的结构生成相应的路由。- 自动路由匹配: 文件系统的层级结构决定了 URL 的路径。例如,在
pages
文件夹下创建about.js
文件,访问/about
路径将会渲染该页面。
- 动态路由: 使用
[]
创建动态路由。例如,pages/posts/[id].js
可以匹配类似/posts/1
、/posts/2
这样的路径。
- Link 组件: 使用
<Link>
组件进行内部页面之间的跳转,避免页面刷新,提升用户体验。例如,<Link href="/about"><a>About</a></Link>
。
- 编程式导航: 通过
next/router
提供的方法,比如router.push()
、router.replace()
等,在代码中进行页面导航和跳转。
- 客户端路由: Next.js 支持客户端路由,允许你在不刷新整个页面的情况下进行导航,提高了页面切换的流畅性。
- 路由参数和查询参数: 可以通过动态路由获取路由参数,也可以使用
router.query
获取 URL 查询参数。
客户端路由
指在浏览器中进行的页面切换和导航,而无需重新加载整个页面。在 Next.js 中,客户端路由通过内置的路由系统来实现。
通过
<Link>
组件或者 next/router
提供的方法(如 router.push()
、router.replace()
),可以在 Next.js 中进行客户端路由的操作。这些方法允许在页面间导航而不刷新整个页面,这对于创建单页应用(SPA)或者提升网站性能都非常有用。
基本路由
在
pages
目录中,每个文件都会成为一个路由。文件的命名和嵌套目录结构决定了生成的 URL 路径。示例
pages/index.js
对应/
pages/about.js
对应/about
pages/blog/index.js
对应/blog
pages/blog/first-post.js
对应/blog/first-post
动态路由
Next.js 支持动态路由,允许你创建基于参数的路由。动态路由文件名包含方括号 (
[]
) 来表示参数。示例
pages/blog/[id].js
对应/blog/:id
pages/user/[username].js
对应/user/:username
在组件中可以通过
useRouter
Hook 来访问路由参数:import { useRouter } from 'next/router'; const BlogPost = () => { const router = useRouter(); const { id } = router.query; return <div>Blog Post ID: {id}</div>; }; export default BlogPost;
动态路由的优先级
静态路由优先级高于动态路由。如果存在冲突,静态路由将被优先匹配。
pages/user/new.js
对应/user/new
pages/user/[username].js
对应/user/:username
在访问
/user/new
时,会匹配到 pages/user/new.js
,而不会匹配到 pages/user/[username].js
。嵌套路由
通过在
pages
目录下创建子目录,可以实现嵌套路由。示例
pages/blog/[id]/comments.js
对应/blog/:id/comments
pages/shop/[category]/[item].js
对应/shop/:category/:item
API 路由
Next.js 支持在
pages/api
目录下创建 API 路由,这些文件会自动作为 API 端点。示例
pages/api/hello.js
对应/api/hello
// pages/api/hello.js export default function handler(req, res) { res.status(200).json({ text: 'Hello' }); }
Catch-All 路由
Next.js 支持 Catch-All 路由,允许捕获所有匹配路径的路由参数。文件名使用三个点 (
...
) 表示。示例
pages/blog/[...slug].js
对应/blog/*
在组件中可以通过
useRouter
Hook 来访问所有路由参数:import { useRouter } from 'next/router'; const BlogPost = () => { const router = useRouter(); const { slug } = router.query; return <div>Blog Post Slug: {slug.join('/')}</div>; }; export default BlogPost;
页面路由的加载顺序
- 静态路由优先。
- 动态路由次之。
- Catch-All 路由最后。
自定义路由
Next.js 也允许通过
next.config.js
配置文件来设置自定义路由。示例
// next.config.js module.exports = { async rewrites() { return [ { source: '/about', destination: '/about-us', // 将 /about 重定向到 /about-us }, ]; }, };