NodeJS系列(10)- Next.js 框架 (三)

发布时间 2023-08-14 18:38:46作者: 垄山小站


在 “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

                > nextjs-demo@0.1.0 build
                > next build

                ...

                ssg1 -> getStaticProps(): data =  { posts: [ { id: 1, title: 'post 1' }, { id: 2, title: 'post 2' } ] }

                ...


        生产模式运行 nextjs-demo 项目,命令如下:
        
            $ npm run start

                > nextjs-demo@0.1.0 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

                > nextjs-demo@0.1.0 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

                > nextjs-demo@0.1.0 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

                > nextjs-demo@0.1.0 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

                > nextjs-demo@0.1.0 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 这样的无服务器平台,它将为您处理此问题。