余震启 發表於 2023-8-14 18:32:00

NodeJS系列(10)- Next.js 框架 (三) | 渲染(Rendering)

<p><br>在 “NodeJS系列(8)- Next.js 框架 (一) | 安装配置、路由(Routing)、页面布局(Layout)” 里,我们简单介绍了 Next.js 的安装配置,创建了 nextjs-demo 项目,讲解和演示了 Next.js 项目的运行、路由(Routing)、页面布局(Layout)等内容。<br><br>在 “NodeJS系列(9)- Next.js 框架 (二) | 国际化 (i18n)、中间件 (Middleware)” 里,我们在 nextjs-demo 项目基础上,讲解和演示了 Next.js 项目的国际化 (i18n)、中间件 (Middleware) 等内容。<br><br>本文继续在 nextjs-demo 项目(Pages Router)基础上,讲解和演示渲染(Rendering)。<br><br>NextJS: https://nextjs.org/<br>NextJS GitHub: https://github.com/vercel/next.js</p>
<h3><br>1. 系统环境</h3>
<p>&nbsp;&nbsp;&nbsp; 操作系统:CentOS 7.9 (x64)<br>&nbsp;&nbsp;&nbsp; NodeJS: 16.20.0<br>&nbsp;&nbsp;&nbsp; NPM: 8.19.4<br>&nbsp;&nbsp;&nbsp; NVM: 0.39.2<br>&nbsp;&nbsp;&nbsp; NextJS: 13.4.12</p>
<h3><br>2. 渲染 (Rendering)</h3>
<p>&nbsp;&nbsp;&nbsp; 默认情况下,Next.js 会预渲染 (Pre-render) 每个页面。这意味着 Next.js 提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。预渲染可以带来更好的性能和 SEO。<br><br>&nbsp;&nbsp;&nbsp; 每个生成的 HTML 都与该页面所需的最小 JavaScript 代码相关联。当浏览器加载页面时,它的 JavaScript 代码会运行,并使页面完全交互,这个过程在 React 中被称为水合(Hydration)。<br><br>&nbsp;&nbsp;&nbsp; Next.js 有两种预渲染形式:静态生成和服务器端渲染。不同之处在于它为页面生成 HTML 的时间。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 静态生成:HTML 在构建时生成,并将在每次请求时重用<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 服务器端渲染:HTML 是根据每个请求生成<br><br>&nbsp;&nbsp;&nbsp; Next.js 允许为每个页面选择要使用的预渲染表单。可以通过对大多数页面使用静态生成,对其他页面使用服务器端渲染来创建 “混合” Next.js 应用程序。<br><br>&nbsp;&nbsp;&nbsp; 出于性能原因,我们建议使用静态生成而不是服务器端渲染。静态生成的页面可以由 CDN 缓存,无需额外配置即可提高性能。但是,在某些情况下,服务器端渲染可能是唯一的选项。<br><br>&nbsp;&nbsp;&nbsp; 还可以将客户端数据提取与静态生成或服务器端渲染一起使用。这意味着页面的某些部分可以完全由客户端 JavaScript 呈现。<br><br>&nbsp;&nbsp;&nbsp; 渲染可以分为以下几种类型:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 服务器端渲染(Server-side Rendering,简称 SSR)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 静态站点生成(Static Site Generation,简称 SSG)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 增量静态再生成(Incremental Static Regeneration,简称 ISR)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) 客户端渲染(Client-side Rendering,简称 CSR)</p>
<p>&nbsp;</p>
<h3>3. 服务器端渲染(Server-side Rendering,简称 SSR)</h3>
<p>&nbsp;&nbsp;&nbsp; 也称为 “动态渲染”,如果页面使用服务器端渲染,则会在每个请求中生成页面 HTML 。<br><br>&nbsp;&nbsp;&nbsp; 页面要使用服务器端渲染,需要导出一个名为 getServerSideProps 的异步函数。假设页面需要预渲染频繁更新的数据(从外部API获取),可以编写 getServerSideProps,它获取这些数据并将其传递给 Page。<br>&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; 示例,创建 src/pages/render/ssr.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre>      export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ message }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {

            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
                </span>&lt;div&gt;{ message }&lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">            )
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This gets called on every request</span>
      export const getServerSideProps = async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Fetch data from external API</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch(`https://.../data`)</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>
            
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
            const data = { message: 'Test message'<span style="color: rgba(0, 0, 0, 1)"> }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示在服务器端控制台,每次请求都会输出一条 log</span>
            console.log('ssr -&gt; getServerSideProps(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass data to the page via props</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: data }
      }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp; 开发模式运行 nextjs-demo 项目,即运行 npm run dev 命令。<br>&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; 使用浏览器访问 http://localhost:3000/render/ssr,显示内容如下:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Home&nbsp; Login&nbsp;&nbsp;&nbsp;&nbsp; # 菜单<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Test message<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Footer<br><br>&nbsp;&nbsp;&nbsp; 再次刷新 http://localhost:3000/render/ssr 页面后,服务端控制台显示如下内容:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ssr -&gt; getServerSideProps(): data =&nbsp; { message: 'Test message' }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ssr -&gt; getServerSideProps(): data =&nbsp; { message: 'Test message' }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:服务器将在每次请求时调用 getServerSideProps</p>
<h3><br>4. 静态站点生成(Static Site Generation,简称 SSG)</h3>
<p>&nbsp;&nbsp;&nbsp; 如果页面使用静态生成,则在构建(build)时生成页面 HTML。这意味着在生产环境中,页面 HTML 是在运行 next build 命令时生成的。该 HTML 将在每个请求中重复使用,它可以通过 CDN 进行缓存。<br><br>&nbsp;&nbsp;&nbsp; 在 Next.js 中,可以静态生成包含或不包含数据的页面。<br><br>&nbsp;&nbsp;&nbsp; <strong>1) 无数据静态生成</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 默认情况下,Next.js 使用静态生成预渲染页面,而不获取数据。示例代码如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export default About = () =&gt; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &lt;div&gt;About&lt;/div&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:此页面不需要获取任何要预渲染的外部数据。在这种情况下,Next.js 在构建时为每页生成一个 HTML 文件。<br><br>&nbsp;&nbsp;&nbsp; <strong>2) 静态生成数据</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 某些页面需要获取外部数据以进行预渲染。有两种情况,其中一种或两种可能都适用。在每种情况下,都可以使用 Next.js 提供的以下函数:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a. 页面内容取决于外部数据,使用 getStaticProps<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b. 页面路径取决于外部数据:使用 getStaticPaths(通常是在 getStaticProps 之外)<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例1,页面内容取决于外部数据,比如:博客页面可能需要从 CMS(内容管理系统)获取博客文章列表。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建 src/pages/render/ssg1.js 文件 (路径里的中间目录,如果不存在,手动创建,下同),代码如下:</p>
<div class="cnblogs_code">
<pre>            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO: Need to fetch `posts` (by calling some API endpoint)</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">       before this page can be pre-rendered.</span>
            export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ posts }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;ul&gt;<span style="color: rgba(0, 0, 0, 1)">
                        {posts.map((post) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                            </span>&lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
<span style="color: rgba(0, 0, 0, 1)">                        ))}
                  </span>&lt;/ul&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function gets called at build time</span>
            export const getStaticProps = async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch('https://.../posts')</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>

                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
                const data =<span style="color: rgba(0, 0, 0, 1)"> { posts: [
                        { id: </span>'1', title: 'post 1'<span style="color: rgba(0, 0, 0, 1)">},
                        { id: </span>'2', title: 'post 2'<span style="color: rgba(0, 0, 0, 1)">}
                  ]
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> next build 时显示在命令行控制台</span>
                console.log('ssg1 -&gt; getStaticProps(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)               
               
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass data to the page via props</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: data }
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:getStaticProps 在构建(next build)时被调用,并在预渲染时将提取的数据传递给页面的属性。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 构建 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run build </p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 build
                &gt; next build

                ...

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

                ...</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 生产模式运行 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run start</p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 start
                &gt; next start

                - ready started server on 0.0.0.0:3000, url: http://localhost:3000

                ...</pre>
</div>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:控制台不显示我们添加在 getStaticProps 函数里的 console.log。这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用浏览器访问 http://localhost:3000/render/ssg1,显示内容如下:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Home&nbsp; Login&nbsp;&nbsp;&nbsp;&nbsp; # 菜单<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post 2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Footer<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再次刷新 http://localhost:3000/render/ssg1 页面后,控制台显示内容没有变化。<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例2,页面路径取决于外部数据,比如:假设只向数据库中添加了一篇博客文章(id 为 1)。在这种情况下,只希望在构建时预渲染 ssg2/1。稍后,可能会添加 id 为 2 的第二个帖子。然后,还需要预渲染 ssg2/2。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建 src/pages/render/ssg2/.js 文件(这里 ".js" 是文件名),代码如下:</p>
<div class="cnblogs_code">
<pre>            export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ post }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;div&gt;{ post.title }&lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function gets called at build time</span>
            export const getStaticPaths = async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch('https://.../posts')</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>

                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
                const data =<span style="color: rgba(0, 0, 0, 1)"> { posts: [
                        { id: </span>'1', title: 'post 1'<span style="color: rgba(0, 0, 0, 1)">},
                        { id: </span>'2', title: 'post 2'<span style="color: rgba(0, 0, 0, 1)">}
                  ]
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> next build 时显示在命令行控制台</span>
                console.log('ssg2 -&gt; getStaticPaths(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)   

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get the paths we want to pre-render based on posts</span>
                const paths = data.posts.map((path) =&gt;<span style="color: rgba(0, 0, 0, 1)"> ({
                  params: { id: path.id },
                }))
               
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> We'll pre-render only these paths at build time.</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { fallback: false } means other routes should 404.</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span> { paths, fallback: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
            }
            
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This also gets called at build time</span>
            export const getStaticProps = async ({params}) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> params contains the post `id`.</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If the route is like /ssg2/1, then params.id is 1</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch(`https://.../posts/${params.id}`)</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>

                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
                const data =<span style="color: rgba(0, 0, 0, 1)"> { posts: [
                        { id: </span>'1', title: 'post 1'<span style="color: rgba(0, 0, 0, 1)">},
                        { id: </span>'2', title: 'post 2'<span style="color: rgba(0, 0, 0, 1)">}
                  ]
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> next build 时显示在命令行控制台</span>
                console.log('ssg2 -&gt; getStaticProps(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)   

                const post </span>= data.posts.find((item) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> item.id ==<span style="color: rgba(0, 0, 0, 1)"> params.id
                })   
                console.log(</span>'ssg2 -&gt; getStaticProps(): post = '<span style="color: rgba(0, 0, 0, 1)">, post)

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass post data to the page via props</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { post } }
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 构建 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run build </p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 build
                &gt; next build

                ...

                ssg2 -&gt; getStaticPaths(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }

                ssg2 -&gt; getStaticProps(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }
                ssg2 -&gt; getStaticProps(): post ={ id: '1', title: 'post 1' }

                ssg2 -&gt; getStaticProps(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }
                ssg2 -&gt; getStaticProps(): post ={ id: '2', title: 'post 2' }

                ...</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 生产模式运行 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run start</p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 start
                &gt; next start

                - ready started server on 0.0.0.0:3000, url: http://localhost:3000

                ...</pre>
</div>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:控制台不显示我们添加在 getStaticProps 函数里的 console.log。这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 运行 nextjs-demo 项目,使用浏览器访问 http://localhost:3000/render/ssg2/2,显示内容如下:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Home&nbsp; Login&nbsp;&nbsp;&nbsp;&nbsp; # 菜单<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post 2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Footer<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再次刷新 http://localhost:3000/render/ssg2/2 页面后,控制台显示内容没有变化。<br><br><br>&nbsp;&nbsp;&nbsp; <strong>3) 静态生成的使用场合</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 建议尽可能使用静态生成(有数据和无数据),因为页面可以一次性构建并由 CDN 提供服务,这比服务器在每次请求时渲染页面要快得多。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以对许多类型的页面使用静态生成,包括:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 市场营销页面<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 博客文章和作品集<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 电子商务产品列表<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) 帮助和文档<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以问自己:“我可以在用户请求之前预呈现这个页面吗?” 如果答案是肯定的,那么应该选择静态生成。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另一方面,如果不能在用户请求之前预先呈现页面,则静态生成不是一个好主意。也许页面会显示频繁更新的数据,并且页面内容会在每次请求时发生变化。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这种情况下,可以执行以下操作之一:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 使用客户端数据获取的静态生成:可以跳过预渲染页面的某些部分,然后使用客户端 JavaScript 填充它们。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 使用服务器端渲染:Next.js 在每个请求上预渲染一个页面。速度会较慢,因为 CDN 无法缓存页面,但预呈现的页面始终是最新的。</p>
<p>&nbsp;</p>
<h3>5. 增量静态再生成(Incremental Static Regeneration,ISR)</h3>
<p>&nbsp;&nbsp;&nbsp; Next.js 允许在创建网站后创建或更新静态页面。增量静态再生成(ISR)能够在每页的基础上使用静态生成,而无需重建整个站点。使用 ISR,可以在扩展到数百万页面的同时保留静态的优势。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:Edge Runtime 目前与 ISR 不兼容,可以通过手动设置缓存控制标头,在重新验证时利用过时的页面。<br><br>&nbsp;&nbsp;&nbsp; 要使用 ISR,需要在 getStaticProps 中添加重新验证 (revalidate) 属性。<br><br>&nbsp;&nbsp;&nbsp; <strong>1) 重新验证 (revalidate)</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; revalidate 是单位为秒的时间值,revalidate 的默认值设置为 false,表示无重新验证,默认情况下仅在调用 revalidate() 函数时重新验证页面。<br>&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,创建 src/pages/render/isr/.js 文件(这里 ".js" 是文件名),代码如下:</p>
<div class="cnblogs_code">
<pre>         export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ post }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;div&gt;{ post.title }&lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            }


            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function gets called at build time on server-side.</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> It may be called again, on a serverless function, if</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the path has not been generated.</span>
            export const getStaticPaths = async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch('https://.../posts')</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>

                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
                const data =<span style="color: rgba(0, 0, 0, 1)"> { posts: [
                        { id: </span>'1', title: 'post 1'<span style="color: rgba(0, 0, 0, 1)">},
                        { id: </span>'2', title: 'post 2'<span style="color: rgba(0, 0, 0, 1)">}
                  ]
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> next build 时显示在命令行控制台,next start 时也可能显示</span>
                console.log('isr -&gt; getStaticPaths(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)   

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get the paths we want to pre-render based on posts</span>
                const paths = data.posts.map((path) =&gt;<span style="color: rgba(0, 0, 0, 1)"> ({
                  params: { id: path.id },
                }))
               
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> We'll pre-render only these paths at build time.</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { fallback: false } means other routes should 404.</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span> { paths, fallback: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function gets called at build time on server-side.</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> It may be called again, on a serverless function, if</span>
            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> revalidation is enabled and a new request comes in</span>
            export const getStaticProps = async ({params}) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const res = await fetch('https://.../posts')</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">const data = await res.json()</span>

                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里不测试外部数据接口,直接使用测试数据</span>
                const data =<span style="color: rgba(0, 0, 0, 1)"> { posts: [
                        { id: </span>'1', title: 'post 1'<span style="color: rgba(0, 0, 0, 1)">},
                        { id: </span>'2', title: 'post 2'<span style="color: rgba(0, 0, 0, 1)">}
                  ]
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> next build 时显示在命令行控制台,next start 时也可能显示</span>
                console.log('isr -&gt; getStaticProps(): data = '<span style="color: rgba(0, 0, 0, 1)">, data)               
               
                const post </span>= data.posts.find((item) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> item.id ==<span style="color: rgba(0, 0, 0, 1)"> params.id
                })   
                console.log(</span>'isr -&gt; getStaticProps(): post = '<span style="color: rgba(0, 0, 0, 1)">, post)

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass post data to the page via props</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
                  props: { post },

                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Next.js will attempt to re-generate the page:</span>
                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> - When a request comes in</span>
                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> - At most once every 10 seconds</span>
                  revalidate: 10, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> In seconds      </span>
<span style="color: rgba(0, 0, 0, 1)">                }
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 构建 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run build</p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 build
                &gt; next build

                ...

                isr -&gt; getStaticPaths(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }
                isr -&gt; getStaticProps(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }
                isr -&gt; getStaticProps(): data ={
                  posts: [ { id: '1', title: 'post 1' }, { id: '2', title: 'post 2' } ]
                }
                isr -&gt; getStaticProps(): post ={ id: '2', title: 'post 2' }
                isr -&gt; getStaticProps(): post ={ id: '1', title: 'post 1' }

                ...</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 生产模式运行 nextjs-demo 项目,命令如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm run start</p>
<div class="cnblogs_code">
<pre>                &gt; nextjs-demo@0.1.0 start
                &gt; next start

                - ready started server on 0.0.0.0:3000, url: http://localhost:3000      </pre>
</div>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:这里不使用开发模式,是因为开发模式下每次页面请求时都会调用 getStaticProps 函数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用浏览器访问 http://localhost:3000/render/isr/1,显示内容如下:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Home&nbsp; Login&nbsp;&nbsp;&nbsp;&nbsp; # 菜单<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Footer<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 再次刷新 http://localhost:3000/render/isr/1 页面后,控制台可能会显示我们添加在 getStaticProps 函数里的 console.log,在初始请求之后到 10 秒之前对页面的任何请求也会被缓存并即时发送。在 10 秒之后,下一个请求仍将显示缓存的(过时的)页面。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Next.js 在后台触发页面的重新生成。一旦页面生成,Next.js 将使缓存无效并显示更新后的页面。如果后台重新生成失败,旧页面仍将保持不变。当对尚未生成的路径发出请求时,Next.js 将在第一个请求中服务器渲染页面。将来的请求将为缓存中的静态文件提供服务。Vercel 上的 ISR 会全局保存缓存并处理回滚。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:检查上游数据提供商是否默认启用了缓存。可能需要禁用(例如 useCdn:false),否则重新验证将无法提取新数据来更新 ISR 缓存。<br><br>&nbsp;&nbsp;&nbsp; <strong>2) 按需重新验证 (或手动重新验证)</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果将重新验证(revalidate)时间设置为 60 秒,所有访问者将在 60 秒内看到相同版本的网站。使页面缓存无效的唯一方法是在 60 秒后有人访问该页面。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从 v12.2.0 开始,Next.js 支持按需增量静态再生成,以手动清除特定页面的 Next.js 缓存。这使得在以下情况下更容易更新您的网站:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 无头 CMS 中的内容已创建或更新<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 电子商务元数据更改(价格、描述、类别、评论等)<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在 getStaticProps 中,不需要指定重新验证(revalidate)即可使用按需重新验证。Next.js 将 revalidate 的默认值设置为 false(无重新验证),默认情况下仅在调用 revalidate()时按需重新验证页面。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:不会对随需应变 ISR 请求执行中间件。相反,在想要重新验证的确切路径上调用 revalidate()。例如,如果您有 pages/render/isr/.js 和 /poster-&gt;/blog/poster-1 的重写,则需要调用 res.revalidate('/blog/ppost-1')。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 按需重新验证 (或手动重新验证) 的方法:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 创建一个只有 Next.js 应用程序知道的秘密令牌。此秘密将用于防止未经授权访问重新验证 API 路由。可以使用以下 URL 结构访问路由(手动或使用 webhook):<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; https://&lt;yoursite.com&gt;/api/revalidate?secret=<token><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 将密钥作为环境变量添加到应用程序中。最后,创建 Revalidation API (src/pages/api/revalidate.js)路由:</p>
<div class="cnblogs_code">
<pre>                export <span style="color: rgba(0, 0, 255, 1)">default</span> handler = async (req, res) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Check for secret to confirm this is a valid request</span>
                  <span style="color: rgba(0, 0, 255, 1)">if</span> (req.query.secret !==<span style="color: rgba(0, 0, 0, 1)"> process.env.MY_SECRET_TOKEN) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span> res.status(401).json({ message: 'Invalid token'<span style="color: rgba(0, 0, 0, 1)"> })
                  }
                  
                  </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this should be the actual path not a rewritten path</span>
                        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> e.g. for "/blog/" this should be "/blog/post-1"</span>
                        await res.revalidate('/path-to-revalidate'<span style="color: rgba(0, 0, 0, 1)">)
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span> res.json({ revalidated: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> })
                  } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If there was an error, Next.js will continue</span>
                        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> to show the last successfully generated page</span>
                        <span style="color: rgba(0, 0, 255, 1)">return</span> res.status(500).send('Error revalidating'<span style="color: rgba(0, 0, 0, 1)">)
                  }
                }</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; <strong>3) 错误处理和重新验证</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果在处理后台重新生成时 getStaticProps 内部出现错误,或者手动抛出错误,则最后一个成功生成的页面将继续显示。在下一个后续请求中,next.js 将重试调用 getStaticProps。</p>
<div class="cnblogs_code">
<pre>            export <span style="color: rgba(0, 0, 255, 1)">default</span> getStaticProps = async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If this request throws an uncaught error, Next.js will</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> not invalidate the currently shown page and</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> retry getStaticProps on the next request.</span>
                const res = await fetch('https://.../posts'<span style="color: rgba(0, 0, 0, 1)">)
                const posts </span>=<span style="color: rgba(0, 0, 0, 1)"> await res.json()
               
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">res.ok) {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If there is a server error, you might want to</span>
                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> throw an error instead of returning so that the cache is not updated</span>
                  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> until the next successful request.</span>
                  <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Error(`Failed to fetch posts, received status ${res.status}`)
                }
               
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If the request was successful, return the posts</span>
                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> and revalidate every 10 seconds.</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
                  props: {
                        posts,
                  },
                  revalidate: </span>10<span style="color: rgba(0, 0, 0, 1)">,
                }
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp; <strong>4) 自托管 ISR</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当使用下一次启动时,增量静态再生成(ISR)可以在自托管 Next.js 网站上开箱即用。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当部署到容器编排器(如 Kubernetes 或 HashiCorp Nomad)时,可以使用这种方法。默认情况下,生成的资产将存储在每个 pod 的内存中。这意味着每个 pod 都有自己的静态文件副本。在特定 pod 被请求击中之前,可能会显示失效数据。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了确保所有 pod 之间的一致性,可以禁用内存中的缓存。这将通知 Next.js 服务器仅利用文件系统中 ISR 生成的资产。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以在 Kubernetes pod 中使用共享网络装载(或类似设置),在不同容器之间重用相同的文件系统缓存。通过共享同一装载,包含 next/image 缓存的 .next 文件夹也将被共享并重新使用。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 要禁用内存内缓存,在 next.config.js 文件设置 isrMemoryCacheSize,代码如下:</p>
<div class="cnblogs_code">
<pre>            module.exports =<span style="color: rgba(0, 0, 0, 1)"> {
                experimental: {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Defaults to 50MB</span>
                  isrMemoryCacheSize: 0<span style="color: rgba(0, 0, 0, 1)">,
                },
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:可能需要考虑多个 pod 之间试图同时更新缓存的竞争条件,这取决于共享装载的配置方式。<br><br></p>
<h3><br>6. 客户端渲染(CSR)</h3>
<p>&nbsp;&nbsp;&nbsp; 在使用 React 的客户端渲染(CSR)中,浏览器下载一个最小的 HTML 页面和该页面所需的 JavaScript。然后使用 JavaScript 来更新 DOM 并渲染页面。当应用程序首次加载时,用户可能会注意到在看到完整页面之前有一点延迟,这是因为在下载、解析和执行所有 JavaScript 之前,页面并没有完全渲染。<br><br>&nbsp;&nbsp;&nbsp; 第一次加载页面后,导航到同一网站上的其他页面通常会更快,因为只需要提取必要的数据,JavaScript 可以重新呈现页面的部分内容,而无需刷新整个页面。<br><br>&nbsp;&nbsp;&nbsp; 在 Next.js 中,有两种方法可以实现客户端渲染:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 在页面中使用 React 的 useEffect)钩子,而不是服务器端渲染方法(getStaticProps 和 getServerSideProps)。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 使用 SWR 或 TanStack Query 等数据获取库在客户端上获取数据(推荐)。<br><br>&nbsp;&nbsp;&nbsp; 以下是在 Next.js 页面中使用 useEffect()的示例:</p>
<div class="cnblogs_code">
<pre>      import React, { useState, useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">
      
      export </span><span style="color: rgba(0, 0, 255, 1)">default</span> () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            
            useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                const fetchData </span>= async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  const response </span>= await fetch('https://api.example.com/data'<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">response.ok) {
                        </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error(`HTTP error!<span style="color: rgba(0, 0, 0, 1)"> status: ${response.status}`)
                  }
                  const result </span>=<span style="color: rgba(0, 0, 0, 1)"> await response.json()
                  setData(result)
                }
            
                fetchData().</span><span style="color: rgba(0, 0, 255, 1)">catch</span>((e) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> handle the error as needed</span>
                  console.error('An error occurred while fetching the data: '<span style="color: rgba(0, 0, 0, 1)">, e)
                })
            }, [])
            
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;p&gt;{data ? `Your data: ${data}` : 'Loading...'}&lt;/p&gt;
      }</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp; 下面是一个使用 SWR 在客户端上获取数据的示例:</p>
<div class="cnblogs_code">
<pre>      import useSWR from 'swr'<span style="color: rgba(0, 0, 0, 1)">

      export </span><span style="color: rgba(0, 0, 255, 1)">default</span> () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            const { data, error, isLoading } </span>=<span style="color: rgba(0, 0, 0, 1)"> useSWR(
                </span>'https://api.example.com/data'<span style="color: rgba(0, 0, 0, 1)">,
                fetcher
            )
            
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (error) <span style="color: rgba(0, 0, 255, 1)">return</span> &lt;p&gt;Failed to load.&lt;/p&gt;
            <span style="color: rgba(0, 0, 255, 1)">if</span> (isLoading) <span style="color: rgba(0, 0, 255, 1)">return</span> &lt;p&gt;Loading...&lt;/p&gt;
            
            <span style="color: rgba(0, 0, 255, 1)">return</span> &lt;p&gt;Your Data: {data}&lt;/p&gt;
      }</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp; 注:CSR 可以会影响 SEO。某些搜索引擎爬网程序可能不执行 JavaScript,因此只能看到应用程序的初始空状态或加载状态。对于互联网连接或设备较慢的用户来说,这也会导致性能问题,因为他们需要等待所有JavaScript加载并运行后才能看到完整的页面。Next.js 提倡一种混合方法,允许您根据应用程序中每个页面的需要,结合使用服务器端渲染、静态站点生成和客户端渲染。</p>
<p>&nbsp;</p>
<h3>7. 自动静态优化</h3>
<p>&nbsp;&nbsp;&nbsp; 如果页面里没有 getServerSideProps、getStaticProps 和 getInitialProps,Next.js 会自动确定页面是静态的(可以预渲染)。Next.js 9.3 或更高版本,官方建议使用 getStaticProps 或 getServerSideProps 来替代 getInitialProps。<br><br>&nbsp;&nbsp;&nbsp; 自动静态优化允许 Next.js 支持混合应用程序,即包含服务器渲染的页面和静态生成的页面。<br>&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; 自动静态优化的主要好处之一是,优化的页面不需要服务器端计算,并且可以从多个 CDN 位置立即流式传输给最终用户,其结果是为用户提供超快速的加载体验。<br><br>&nbsp;&nbsp;&nbsp; <strong>1) 工作原理</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果页面中存在 getServerSideProps 或 getInitialProps,Next.js 将切换为按请求渲染页面(即服务器端渲染)。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果不是上述情况,Next.js 将通过将页面预渲染为静态 HTML 来自动静态优化页面。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在预提交期间 (prerendering),路由器的查询对象将为空,因为在此阶段我们没有要提供的查询信息。水合(Hydration)后,Next.js 将触发对应用程序的更新,以在查询对象中提供路由参数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 水合触发另一个渲染后将更新查询的情况有:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 页面是一个动态路由<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 页面的 URL 中包含查询值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 重写是在 next.config.js 中配置的,因为这些重写可能具有可能需要在查询中解析和提供的参数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了能够区分查询是否已完全更新并准备好使用,可以利用 next/router 上的 isReady 字段。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:在使用 getStaticProps 的页面中添加动态路由的参数在查询对象中始终可用。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 命令 next build 将为静态优化的页面生成 .html 文件。例如,pages/about.js 的结果是:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .next/server/pages/about.html<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果将 getServerSideProps 添加到页面中,那么它将是 JavaScript,如下所示:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .next/server/pages/about.js<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; <strong>2) 注意事项</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 如果有一个带有 getStaticProps 或 getServerSideProps 的自定义应用程序,则此优化将在没有静态生成的页面中关闭。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 如果有一个带有 getStaticProps 或 getServerSideProps 的自定义文档,请确保在假设页面是服务器端呈现之前检查是否定义了 ctx.req。对于预渲染的页面,ctx.req 将是未定义的。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 在路由器的 isReady 字段为 true 之前,请避免在渲染树中的 next/router 上使用 asPath 值。静态优化的页面只知道客户端上的路径而不知道服务器上的路径,因此将其用作道具可能会导致不匹配错误。活动类名示例演示了使用 asPath 作为属性的一种方法。<br><br></p>
<h3><br>8. Edge 和 Node.js Runtime (运行环境)</h3>
<p>&nbsp;&nbsp;&nbsp; 在 Next.js 的上下文中,运行时是指代码在执行过程中可用的一组库、API 和通用功能。<br><br>&nbsp;&nbsp;&nbsp; 在服务器上,有两个 Runtime 可以呈现部分应用程序代码:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) Node.js Runtime(默认)可以访问生态系统中的所有 Node.js API 和兼容包<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) Edge Runtime 基于 Web API<br><br>&nbsp;&nbsp;&nbsp; 默认情况下,应用程序目录使用 Node.js 运行时。但是,可以根据每条路由选择不同的运行时(例如 Edge)。<br><br>&nbsp;&nbsp;&nbsp; <strong>1) Runtime 差异</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在选择运行时时需要考虑许多因素。此表显示了主要差异。如果想对差异进行更深入的分析,请查看下面的部分。<br><br></p>
<div class="cnblogs_code">
<pre>                            Node    Serverless   Edge
            Cold Boot       /      ~250ms      Instant
            HTTP StreamingYes      Yes         Yes
            IO            All      All         fetch
            Scalability   /      High      Highest
            Security      Normal   High      High
            Latency         Normal   Low         Lowest
            npm Packages    All      All         A smaller subset</pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp; <strong>2) Edge Runtime</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在 Next.js 中,轻量级 Edge Runtime 是可用 Node.js API 的子集。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果需要以小而简单的功能以低延迟提供动态、个性化的内容,Edge Runtime 是理想的选择。Edge Runtime 的速度来自于它对资源的最小使用,但在许多情况下这可能会受到限制。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例如,在 Vercel上 的 Edge Runtime 中执行的代码不能超过 1MB 到 4MB,此限制包括导入的包、字体和文件,并且会根据您的部署基础结构而有所不同。<br><br>&nbsp;&nbsp;&nbsp; <strong>3) Node.js Runtime</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用 Node.js Runtime 可以访问所有 Node.js API,以及所有依赖它们的 npm 包。但是,它的启动速度不如使用 Edge 运行时的路由快。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将 Next.js 应用程序部署到 Node.js 服务器需要管理、扩展和配置基础设施。或者,可以考虑将 Next.js 应用程序部署到像 Vercel 这样的无服务器平台,它将为您处理此问题。<br><br><br></p><br><br>
来源:https://www.cnblogs.com/tkuang/p/17629452.html
頁: [1]
查看完整版本: NodeJS系列(10)- Next.js 框架 (三) | 渲染(Rendering)