Logo
Section 1 / 4

静态渲染与缓存基础

了解如何使用 fetch 缓存、generateStaticParams 和 generateMetadata 来构建完全静态且对 SEO 友好的 Next.js 页面。

导入

静态渲染与缓存基础

了解如何使用 fetch 缓存、generateStaticParams 和 generateMetadata 来构建完全静态且对 SEO 友好的 Next.js 页面。

用于获取所有文章的可复用函数

1// app/lib/data.js 2 3// 此包作为防护机制。如果此文件被导入到客户端组件中,将导致构建错误。 4import 'server-only'; 5 6export async function getPosts() { 7 // 为了使此请求变为静态,我们必须添加 cache 选项。 8 const res = await fetch('https://jsonplaceholder.typicode.com/posts', { 9 cache: 'force-cache', 10 }); 11 return res.json(); 12}

静态文章页面组件

1// app/posts/page.js 2import { getPosts } from '../lib/data'; 3 4export default async function PostsPage() { 5 const posts = await getPosts(); 6 const buildTime = new Date().toLocaleTimeString(); 7 8 return ( 9 <div> 10 <h1>所有文章</h1> 11 <p>此页面生成于:{buildTime}</p> 12 <ul> 13 {posts.map((post) => ( 14 <li key={post.id}>{post.title}</li> 15 ))} 16 </ul> 17 </div> 18 ); 19}

用于获取单篇文章的可复用函数

1// lib/data.js (添加此新函数) 2export async function getPost(slug) { 3 const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${slug}`, { 4 cache: 'force-cache', 5 }); 6 return res.json(); 7}

No configuration found for ID: 82fce6077b07fafda5218c6e915a6c85f09fda7fd11eed55a42501fa466b122e

为博客文章页面添加动态标题

1// app/blog/[slug]/page.js (将此函数添加到上方文件中) 2 3export async function generateMetadata({ params }) { 4 const { slug } = await params; 5 const post = await getPost(slug); 6 7 return { 8 title: post.title, 9 }; 10}

No configuration found for ID: b75358764c338ac58717dfb21a3952bc9693ba311e32d6cadb7bd69f1799e9fe

静态对象

1export const metadata = { 2 title: '博客', 3 description: '我们网站上的所有博客文章。' 4}

No configuration found for ID: 164f48e8f519f0eb549e1835379ea04f40f683d84816dfabdd6703bb6fe589a5

显示解决方案

让我们逐步走完完整的解决方案。

1. lib/albums.js 文件

以下代码提供了两个异步工具函数,通过缓存请求从 JSONPlaceholder API 获取所有相册或指定 ID 的相册。

完整的相册数据获取模块

1// lib/albums.js 2import 'server-only'; 3 4// 获取所有相册 5export async function getAlbums() { 6 const res = await fetch('https://jsonplaceholder.typicode.com/albums', { 7 cache: 'force-cache', 8 }); 9 return res.json(); 10} 11 12// 根据 ID 获取单个相册 13export async function getAlbum(id) { 14 const res = await fetch(`https://jsonplaceholder.typicode.com/albums/${id}`, { 15 cache: 'force-cache', 16 }); 17 return res.json(); 18}

说明:

  • 第 2 行: 确保此文件仅在服务器端使用,并防止其被导入到客户端组件中。

  • 第 5–10 行:getAlbums() 获取所有相册列表,并使用 { cache: 'force-cache' } 明确告诉 Next.js 在构建时缓存数据。

  • 第 13–18 行:getAlbum(id) 根据 ID 获取单个相册,并同样使用 { cache: 'force-cache' } 确保数据在静态生成时被缓存。

2. app/albums/page.js 文件

以下代码渲染一个服务端页面,该页面获取所有相册并以链接形式在列表中展示它们。

创建相册索引页面

1// app/albums/page.js 2import Link from 'next/link'; 3import { getAlbums } from '../lib/albums'; 4 5export default async function AlbumsPage() { 6 const albums = await getAlbums(); 7 8 return ( 9 <main> 10 <h1>照片相册</h1> 11 <ul> 12 {albums.map((album) => ( 13 <li key={album.id}> 14 <Link href={`/albums/${album.id}`}>{album.title}</Link> 15 </li> 16 ))} 17 </ul> 18 </main> 19 ); 20}

说明:

  • 第 3 行:getAlbums 函数获取所有相册列表。

  • 第 5–20 行:AlbumsPage 组件调用该函数,并对返回的数组进行映射,生成指向各个唯一相册详情页的链接列表。

3. app/albums/[id]/page.js 文件

以下代码静态生成单个相册页面,为每个页面设置动态元数据,并在构建时间戳下渲染选定的相册。

创建相册详情页面

1// app/albums/[id]/page.js 2import { getAlbums, getAlbum } from '../../lib/albums'; 3 4// 预构建所有相册页面 5export async function generateStaticParams() { 6 const albums = await getAlbums(); 7 return albums.map((album) => ({ 8 id: album.id.toString(), 9 })); 10} 11 12// 为每个页面生成唯一的元数据 13export async function generateMetadata({ params }) { 14 const { id } = await params; 15 const album = await getAlbum(id); 16 return { 17 title: `相册: ${album.title}`, 18 }; 19} 20 21// 渲染页面组件 22export default async function AlbumPage({ params }) { 23 const { id } = await params; 24 const album = await getAlbum(id); 25 26 const buildTime = new Date().toLocaleTimeString(); 27 28 return ( 29 <main> 30 <h1>{album.title}</h1> 31 <p style={{ color: 'gray' }}>生成于:{buildTime}</p> 32 </main> 33 ); 34}

说明:

  • 第 5–10 行:generateStaticParams 函数在构建时运行一次。它调用我们的 getAlbums 函数(该函数使用缓存的 fetch)来获取所有相册 ID 列表,从而告知 Next.js 哪些相册页面需要预先构建。

  • 第 13–19 行:generateMetadata 函数获取特定相册的数据,以创建页面 <head> 中的唯一 <title>。

  • 第 22–34 行:AlbumPage 组件获取相同的数据以渲染最终 UI,包括 buildTime 常量,以证明页面是静态的。尽管我们在代码中两次调用 getAlbum(),但 Next.js 的请求记忆化确保了每个页面的实际网络 fetch 只会发生一次。

使用 React 服务器组件进行渲染

服务器端渲染 (SSR)