在 “NodeJS系列(8)- Next.js 框架 (一)” 里,我们简单介绍了 Next.js 的安装配置,创建了 nextjs-demo 项目,讲解和演示了 Next.js 项目的运行、路由(Routing)、页面布局(Layout)等内容。
在 “NodeJS系列(9)- Next.js 框架 (二)” 里,我们在 nextjs-demo 项目基础上,讲解和演示了 Next.js 项目的国际化 (i18n)、中间件 (Middleware) 等内容。
本文继续在 nextjs-demo 项目(Pages Router)基础上,讲解和演示渲染(Rendering)。
NextJS: https://nextjs.org/
NextJS GitHub: https://github.com/vercel/next.js
1. 系统环境
操作系统:CentOS 7.9 (x64)
NodeJS: 16.20.0
NPM: 8.19.4
NVM: 0.39.2
NextJS: 13.4.12
2. 渲染 (Rendering)
默认情况下,Next.js 会预渲染 (Pre-render) 每个页面。这意味着 Next.js 提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。预渲染可以带来更好的性能和 SEO。
每个生成的 HTML 都与该页面所需的最小 JavaScript 代码相关联。当浏览器加载页面时,它的 JavaScript 代码会运行,并使页面完全交互,这个过程在 React 中被称为水合(Hydration)。
Next.js 有两种预渲染形式:静态生成和服务器端渲染。不同之处在于它为页面生成 HTML 的时间。
(1) 静态生成:HTML 在构建时生成,并将在每次请求时重用
(2) 服务器端渲染:HTML 是根据每个请求生成
Next.js 允许为每个页面选择要使用的预渲染表单。可以通过对大多数页面使用静态生成,对其他页面使用服务器端渲染来创建 “混合” Next.js 应用程序。
出于性能原因,我们建议使用静态生成而不是服务器端渲染。静态生成的页面可以由 CDN 缓存,无需额外配置即可提高性能。但是,在某些情况下,服务器端渲染可能是唯一的选项。
还可以将客户端数据提取与静态生成或服务器端渲染一起使用。这意味着页面的某些部分可以完全由客户端 JavaScript 呈现。
渲染可以分为以下几种类型:
(1) 服务器端渲染(Server-side Rendering,简称 SSR)
(2) 静态站点生成(Static Site Generation,简称 SSG)
(3) 增量静态再生成(Incremental Static Regeneration,简称 ISR)
(4) 客户端渲染(Client-side Rendering,简称 CSR)
3. 服务器端渲染(Server-side Rendering,简称 SSR)
也称为 “动态渲染”,如果页面使用服务器端渲染,则会在每个请求中生成页面 HTML 。
页面要使用服务器端渲染,需要导出一个名为 getServerSideProps 的异步函数。假设页面需要预渲染频繁更新的数据(从外部API获取),可以编写 getServerSideProps,它获取这些数据并将其传递给 Page。
示例,创建 src/pages/render/ssr.js 文件,代码如下:
export default ({ message }) => { return ( <div>{ message }</div> ) } // This gets called on every request export const getServerSideProps = async () => { // Fetch data from external API //const res = await fetch(`https://.../data`) //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { message: 'Test message' } // 显示在服务器端控制台,每次请求都会输出一条 log console.log('ssr -> getServerSideProps(): data = ', data) // Pass data to the page via props return { props: data } }
开发模式运行 nextjs-demo 项目,即运行 npm run dev 命令。
使用浏览器访问 http://localhost:3000/render/ssr,显示内容如下:
Home Login # 菜单
Test message
Footer
再次刷新 http://localhost:3000/render/ssr 页面后,服务端控制台显示如下内容:
...
ssr -> getServerSideProps(): data = { message: 'Test message' }
ssr -> getServerSideProps(): data = { message: 'Test message' }
注:服务器将在每次请求时调用 getServerSideProps
4. 静态站点生成(Static Site Generation,简称 SSG)
如果页面使用静态生成,则在构建(build)时生成页面 HTML。这意味着在生产环境中,页面 HTML 是在运行 next build 命令时生成的。该 HTML 将在每个请求中重复使用,它可以通过 CDN 进行缓存。
在 Next.js 中,可以静态生成包含或不包含数据的页面。
1) 无数据静态生成
默认情况下,Next.js 使用静态生成预渲染页面,而不获取数据。示例代码如下:
export default About = () => {
return <div>About</div>
}
注:此页面不需要获取任何要预渲染的外部数据。在这种情况下,Next.js 在构建时为每页生成一个 HTML 文件。
2) 静态生成数据
某些页面需要获取外部数据以进行预渲染。有两种情况,其中一种或两种可能都适用。在每种情况下,都可以使用 Next.js 提供的以下函数:
a. 页面内容取决于外部数据,使用 getStaticProps
b. 页面路径取决于外部数据:使用 getStaticPaths(通常是在 getStaticProps 之外)
示例1,页面内容取决于外部数据,比如:博客页面可能需要从 CMS(内容管理系统)获取博客文章列表。
创建 src/pages/render/ssg1.js 文件 (路径里的中间目录,如果不存在,手动创建,下同),代码如下:
// TODO: Need to fetch `posts` (by calling some API endpoint) // before this page can be pre-rendered. export default ({ posts }) => { return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) } // This function gets called at build time export const getStaticProps = async () => { // Call an external API endpoint to get posts //const res = await fetch('https://.../posts') //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { posts: [ { id: '1', title: 'post 1'}, { id: '2', title: 'post 2'} ] } // next build 时显示在命令行控制台 console.log('ssg1 -> getStaticProps(): data = ', data) // Pass data to the page via props return { props: data } }
注:getStaticProps 在构建(next build)时被调用,并在预渲染时将提取的数据传递给页面的属性。
构建 nextjs-demo 项目,命令如下:
$ npm run build
> [email protected] build > next build ... ssg1 -> getStaticProps(): data = { posts: [ { id: 1, title: 'post 1' }, { id: 2, title: 'post 2' } ] } ...
生产模式运行 nextjs-demo 项目,命令如下:
$ npm run start
> [email protected] start > next start - ready started server on 0.0.0.0:3000, url: http://localhost:3000 ...
注:控制台不显示我们添加在 getStaticProps 函数里的 console.log。这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。
使用浏览器访问 http://localhost:3000/render/ssg1,显示内容如下:
Home Login # 菜单
post 1
post 2
Footer
再次刷新 http://localhost:3000/render/ssg1 页面后,控制台显示内容没有变化。
示例2,页面路径取决于外部数据,比如:假设只向数据库中添加了一篇博客文章(id 为 1)。在这种情况下,只希望在构建时预渲染 ssg2/1。稍后,可能会添加 id 为 2 的第二个帖子。然后,还需要预渲染 ssg2/2。
创建 src/pages/render/ssg2/[id].js 文件(这里 "[id].js" 是文件名),代码如下:
export default ({ post }) => { return ( <div>{ post.title }</div> ) } // This function gets called at build time export const getStaticPaths = async () => { // Call an external API endpoint to get posts //const res = await fetch('https://.../posts') //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { posts: [ { id: '1', title: 'post 1'}, { id: '2', title: 'post 2'} ] } // next build 时显示在命令行控制台 console.log('ssg2 -> getStaticPaths(): data = ', data) // Get the paths we want to pre-render based on posts const paths = data.posts.map((path) => ({ params: { id: path.id }, })) // We'll pre-render only these paths at build time. // { fallback: false } means other routes should 404. return { paths, fallback: false } } // This also gets called at build time export const getStaticProps = async ({params}) => { // params contains the post `id`. // If the route is like /ssg2/1, then params.id is 1 //const res = await fetch(`https://.../posts/${params.id}`) //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { posts: [ { id: '1', title: 'post 1'}, { id: '2', title: 'post 2'} ] } // next build 时显示在命令行控制台 console.log('ssg2 -> getStaticProps(): data = ', data) const post = data.posts.find((item) => { return item.id == params.id }) console.log('ssg2 -> getStaticProps(): post = ', post) // Pass post data to the page via props return { props: { post } } }
构建 nextjs-demo 项目,命令如下:
$ npm run build
> [email protected] build > next build ... ssg2 -> getStaticPaths(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } ssg2 -> getStaticProps(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } ssg2 -> getStaticProps(): post = { id: '1', title: 'post 1' } ssg2 -> getStaticProps(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } ssg2 -> getStaticProps(): post = { id: '2', title: 'post 2' } ...
生产模式运行 nextjs-demo 项目,命令如下:
$ npm run start
> [email protected] start > next start - ready started server on 0.0.0.0:3000, url: http://localhost:3000 ...
注:控制台不显示我们添加在 getStaticProps 函数里的 console.log。这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。
运行 nextjs-demo 项目,使用浏览器访问 http://localhost:3000/render/ssg2/2,显示内容如下:
Home Login # 菜单
post 2
Footer
再次刷新 http://localhost:3000/render/ssg2/2 页面后,控制台显示内容没有变化。
3) 静态生成的使用场合
建议尽可能使用静态生成(有数据和无数据),因为页面可以一次性构建并由 CDN 提供服务,这比服务器在每次请求时渲染页面要快得多。
可以对许多类型的页面使用静态生成,包括:
(1) 市场营销页面
(2) 博客文章和作品集
(3) 电子商务产品列表
(4) 帮助和文档
可以问自己:“我可以在用户请求之前预呈现这个页面吗?” 如果答案是肯定的,那么应该选择静态生成。
另一方面,如果不能在用户请求之前预先呈现页面,则静态生成不是一个好主意。也许页面会显示频繁更新的数据,并且页面内容会在每次请求时发生变化。
在这种情况下,可以执行以下操作之一:
(1) 使用客户端数据获取的静态生成:可以跳过预渲染页面的某些部分,然后使用客户端 JavaScript 填充它们。
(2) 使用服务器端渲染:Next.js 在每个请求上预渲染一个页面。速度会较慢,因为 CDN 无法缓存页面,但预呈现的页面始终是最新的。
5. 增量静态再生成(Incremental Static Regeneration,ISR)
Next.js 允许在创建网站后创建或更新静态页面。增量静态再生成(ISR)能够在每页的基础上使用静态生成,而无需重建整个站点。使用 ISR,可以在扩展到数百万页面的同时保留静态的优势。
注:Edge Runtime 目前与 ISR 不兼容,可以通过手动设置缓存控制标头,在重新验证时利用过时的页面。
要使用 ISR,需要在 getStaticProps 中添加重新验证 (revalidate) 属性。
1) 重新验证 (revalidate)
revalidate 是单位为秒的时间值,revalidate 的默认值设置为 false,表示无重新验证,默认情况下仅在调用 revalidate() 函数时重新验证页面。
示例,创建 src/pages/render/isr/[id].js 文件(这里 "[id].js" 是文件名),代码如下:
export default ({ post }) => { return ( <div>{ post.title }</div> ) } // This function gets called at build time on server-side. // It may be called again, on a serverless function, if // the path has not been generated. export const getStaticPaths = async () => { // Call an external API endpoint to get posts //const res = await fetch('https://.../posts') //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { posts: [ { id: '1', title: 'post 1'}, { id: '2', title: 'post 2'} ] } // next build 时显示在命令行控制台,next start 时也可能显示 console.log('isr -> getStaticPaths(): data = ', data) // Get the paths we want to pre-render based on posts const paths = data.posts.map((path) => ({ params: { id: path.id }, })) // We'll pre-render only these paths at build time. // { fallback: false } means other routes should 404. return { paths, fallback: false } } // This function gets called at build time on server-side. // It may be called again, on a serverless function, if // revalidation is enabled and a new request comes in export const getStaticProps = async ({params}) => { // Call an external API endpoint to get posts //const res = await fetch('https://.../posts') //const data = await res.json() // 这里不测试外部数据接口,直接使用测试数据 const data = { posts: [ { id: '1', title: 'post 1'}, { id: '2', title: 'post 2'} ] } // next build 时显示在命令行控制台,next start 时也可能显示 console.log('isr -> getStaticProps(): data = ', data) const post = data.posts.find((item) => { return item.id == params.id }) console.log('isr -> getStaticProps(): post = ', post) // Pass post data to the page via props return { props: { post }, // Next.js will attempt to re-generate the page: // - When a request comes in // - At most once every 10 seconds revalidate: 10, // In seconds } }
构建 nextjs-demo 项目,命令如下:
$ npm run build
> [email protected] build > next build ... isr -> getStaticPaths(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } isr -> getStaticProps(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } isr -> getStaticProps(): data = { posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ] } isr -> getStaticProps(): post = { id: '2', title: 'post 2' } isr -> getStaticProps(): post = { id: '1', title: 'post 1' } ...
生产模式运行 nextjs-demo 项目,命令如下:
$ npm run start
> [email protected] start > next start - ready started server on 0.0.0.0:3000, url: http://localhost:3000
注:这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。
使用浏览器访问 http://localhost:3000/render/isr/1,显示内容如下:
Home Login # 菜单
post 1
Footer
再次刷新 http://localhost:3000/render/isr/1 页面后,控制台可能会显示我们添加在 getStaticProps 函数里的 console.log,在初始请求之后到 10 秒之前对页面的任何请求也会被缓存并即时发送。在 10 秒之后,下一个请求仍将显示缓存的(过时的)页面。
Next.js 在后台触发页面的重新生成。一旦页面生成,Next.js 将使缓存无效并显示更新后的页面。如果后台重新生成失败,旧页面仍将保持不变。当对尚未生成的路径发出请求时,Next.js 将在第一个请求中服务器渲染页面。将来的请求将为缓存中的静态文件提供服务。Vercel 上的 ISR 会全局保存缓存并处理回滚。
注:检查上游数据提供商是否默认启用了缓存。可能需要禁用(例如 useCdn:false),否则重新验证将无法提取新数据来更新 ISR 缓存。
2) 按需重新验证 (或手动重新验证)
如果将重新验证(revalidate)时间设置为 60 秒,所有访问者将在 60 秒内看到相同版本的网站。使页面缓存无效的唯一方法是在 60 秒后有人访问该页面。
从 v12.2.0 开始,Next.js 支持按需增量静态再生成,以手动清除特定页面的 Next.js 缓存。这使得在以下情况下更容易更新您的网站:
(1) 无头 CMS 中的内容已创建或更新
(2) 电子商务元数据更改(价格、描述、类别、评论等)
在 getStaticProps 中,不需要指定重新验证(revalidate)即可使用按需重新验证。Next.js 将 revalidate 的默认值设置为 false(无重新验证),默认情况下仅在调用 revalidate()时按需重新验证页面。
注:不会对随需应变 ISR 请求执行中间件。相反,在想要重新验证的确切路径上调用 revalidate()。例如,如果您有 pages/render/isr/[id].js 和 /poster->/blog/poster-1 的重写,则需要调用 res.revalidate('/blog/ppost-1')。
按需重新验证 (或手动重新验证) 的方法:
(1) 创建一个只有 Next.js 应用程序知道的秘密令牌。此秘密将用于防止未经授权访问重新验证 API 路由。可以使用以下 URL 结构访问路由(手动或使用 webhook):
https://<yoursite.com>/api/revalidate?secret=<token>
(2) 将密钥作为环境变量添加到应用程序中。最后,创建 Revalidation API (src/pages/api/revalidate.js)路由:
export default handler = async (req, res) => { // Check for secret to confirm this is a valid request if (req.query.secret !== process.env.MY_SECRET_TOKEN) { return res.status(401).json({ message: 'Invalid token' }) } try { // this should be the actual path not a rewritten path // e.g. for "/blog/[slug]" this should be "/blog/post-1" await res.revalidate('/path-to-revalidate') return res.json({ revalidated: true }) } catch (err) { // If there was an error, Next.js will continue // to show the last successfully generated page return res.status(500).send('Error revalidating') } }
3) 错误处理和重新验证
如果在处理后台重新生成时 getStaticProps 内部出现错误,或者手动抛出错误,则最后一个成功生成的页面将继续显示。在下一个后续请求中,next.js 将重试调用 getStaticProps。
export default getStaticProps = async () => { // If this request throws an uncaught error, Next.js will // not invalidate the currently shown page and // retry getStaticProps on the next request. const res = await fetch('https://.../posts') const posts = await res.json() if (!res.ok) { // If there is a server error, you might want to // throw an error instead of returning so that the cache is not updated // until the next successful request. throw new Error(`Failed to fetch posts, received status ${res.status}`) } // If the request was successful, return the posts // and revalidate every 10 seconds. return { props: { posts, }, revalidate: 10, } }
4) 自托管 ISR
当使用下一次启动时,增量静态再生成(ISR)可以在自托管 Next.js 网站上开箱即用。
当部署到容器编排器(如 Kubernetes 或 HashiCorp Nomad)时,可以使用这种方法。默认情况下,生成的资产将存储在每个 pod 的内存中。这意味着每个 pod 都有自己的静态文件副本。在特定 pod 被请求击中之前,可能会显示失效数据。
为了确保所有 pod 之间的一致性,可以禁用内存中的缓存。这将通知 Next.js 服务器仅利用文件系统中 ISR 生成的资产。
可以在 Kubernetes pod 中使用共享网络装载(或类似设置),在不同容器之间重用相同的文件系统缓存。通过共享同一装载,包含 next/image 缓存的 .next 文件夹也将被共享并重新使用。
要禁用内存内缓存,在 next.config.js 文件设置 isrMemoryCacheSize,代码如下:
module.exports = { experimental: { // Defaults to 50MB isrMemoryCacheSize: 0, }, }
注:可能需要考虑多个 pod 之间试图同时更新缓存的竞争条件,这取决于共享装载的配置方式。
6. 客户端渲染(CSR)
在使用 React 的客户端渲染(CSR)中,浏览器下载一个最小的 HTML 页面和该页面所需的 JavaScript。然后使用 JavaScript 来更新 DOM 并渲染页面。当应用程序首次加载时,用户可能会注意到在看到完整页面之前有一点延迟,这是因为在下载、解析和执行所有 JavaScript 之前,页面并没有完全渲染。
第一次加载页面后,导航到同一网站上的其他页面通常会更快,因为只需要提取必要的数据,JavaScript 可以重新呈现页面的部分内容,而无需刷新整个页面。
在 Next.js 中,有两种方法可以实现客户端渲染:
(1) 在页面中使用 React 的 useEffect)钩子,而不是服务器端渲染方法(getStaticProps 和 getServerSideProps)。
(2) 使用 SWR 或 TanStack Query 等数据获取库在客户端上获取数据(推荐)。
以下是在 Next.js 页面中使用 useEffect()的示例:
import React, { useState, useEffect } from 'react' export default () => { const [data, setData] = useState(null) useEffect(() => { const fetchData = async () => { const response = await fetch('https://api.example.com/data') if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result = await response.json() setData(result) } fetchData().catch((e) => { // handle the error as needed console.error('An error occurred while fetching the data: ', e) }) }, []) return <p>{data ? `Your data: ${data}` : 'Loading...'}</p> }
下面是一个使用 SWR 在客户端上获取数据的示例:
import useSWR from 'swr' export default () => { const { data, error, isLoading } = useSWR( 'https://api.example.com/data', fetcher ) if (error) return <p>Failed to load.</p> if (isLoading) return <p>Loading...</p> return <p>Your Data: {data}</p> }
注:CSR 可以会影响 SEO。某些搜索引擎爬网程序可能不执行 JavaScript,因此只能看到应用程序的初始空状态或加载状态。对于互联网连接或设备较慢的用户来说,这也会导致性能问题,因为他们需要等待所有JavaScript加载并运行后才能看到完整的页面。Next.js 提倡一种混合方法,允许您根据应用程序中每个页面的需要,结合使用服务器端渲染、静态站点生成和客户端渲染。
7. 自动静态优化
如果页面里没有 getServerSideProps、getStaticProps 和 getInitialProps,Next.js 会自动确定页面是静态的(可以预渲染)。Next.js 9.3 或更高版本,官方建议使用 getStaticProps 或 getServerSideProps 来替代 getInitialProps。
自动静态优化允许 Next.js 支持混合应用程序,即包含服务器渲染的页面和静态生成的页面。
自动静态优化的主要好处之一是,优化的页面不需要服务器端计算,并且可以从多个 CDN 位置立即流式传输给最终用户,其结果是为用户提供超快速的加载体验。
1) 工作原理
如果页面中存在 getServerSideProps 或 getInitialProps,Next.js 将切换为按请求渲染页面(即服务器端渲染)。
如果不是上述情况,Next.js 将通过将页面预渲染为静态 HTML 来自动静态优化页面。
在预提交期间 (prerendering),路由器的查询对象将为空,因为在此阶段我们没有要提供的查询信息。水合(Hydration)后,Next.js 将触发对应用程序的更新,以在查询对象中提供路由参数。
水合触发另一个渲染后将更新查询的情况有:
(1) 页面是一个动态路由
(2) 页面的 URL 中包含查询值
(3) 重写是在 next.config.js 中配置的,因为这些重写可能具有可能需要在查询中解析和提供的参数。
为了能够区分查询是否已完全更新并准备好使用,可以利用 next/router 上的 isReady 字段。
注:在使用 getStaticProps 的页面中添加动态路由的参数在查询对象中始终可用。
命令 next build 将为静态优化的页面生成 .html 文件。例如,pages/about.js 的结果是:
.next/server/pages/about.html
如果将 getServerSideProps 添加到页面中,那么它将是 JavaScript,如下所示:
.next/server/pages/about.js
2) 注意事项
(1) 如果有一个带有 getStaticProps 或 getServerSideProps 的自定义应用程序,则此优化将在没有静态生成的页面中关闭。
(2) 如果有一个带有 getStaticProps 或 getServerSideProps 的自定义文档,请确保在假设页面是服务器端呈现之前检查是否定义了 ctx.req。对于预渲染的页面,ctx.req 将是未定义的。
(3) 在路由器的 isReady 字段为 true 之前,请避免在渲染树中的 next/router 上使用 asPath 值。静态优化的页面只知道客户端上的路径而不知道服务器上的路径,因此将其用作道具可能会导致不匹配错误。活动类名示例演示了使用 asPath 作为属性的一种方法。
8. Edge 和 Node.js Runtime (运行环境)
在 Next.js 的上下文中,运行时是指代码在执行过程中可用的一组库、API 和通用功能。
在服务器上,有两个 Runtime 可以呈现部分应用程序代码:
(1) Node.js Runtime(默认)可以访问生态系统中的所有 Node.js API 和兼容包
(2) Edge Runtime 基于 Web API
默认情况下,应用程序目录使用 Node.js 运行时。但是,可以根据每条路由选择不同的运行时(例如 Edge)。
1) Runtime 差异
在选择运行时时需要考虑许多因素。此表显示了主要差异。如果想对差异进行更深入的分析,请查看下面的部分。
Node Serverless Edge Cold Boot / ~250ms Instant HTTP Streaming Yes Yes Yes IO All All fetch Scalability / High Highest Security Normal High High Latency Normal Low Lowest npm Packages All All A smaller subset
2) Edge Runtime
在 Next.js 中,轻量级 Edge Runtime 是可用 Node.js API 的子集。
如果需要以小而简单的功能以低延迟提供动态、个性化的内容,Edge Runtime 是理想的选择。Edge Runtime 的速度来自于它对资源的最小使用,但在许多情况下这可能会受到限制。
例如,在 Vercel上 的 Edge Runtime 中执行的代码不能超过 1MB 到 4MB,此限制包括导入的包、字体和文件,并且会根据您的部署基础结构而有所不同。
3) Node.js Runtime
使用 Node.js Runtime 可以访问所有 Node.js API,以及所有依赖它们的 npm 包。但是,它的启动速度不如使用 Edge 运行时的路由快。
将 Next.js 应用程序部署到 Node.js 服务器需要管理、扩展和配置基础设施。或者,可以考虑将 Next.js 应用程序部署到像 Vercel 这样的无服务器平台,它将为您处理此问题。