SSR,SSG,ISR在Next.js中的应用

2,323 阅读6分钟

一、介绍

从最开始的客户端渲染(Client Side Rendering/CSR)到最近比较火的服务端渲染(Server side Rendering/SSR), 三大前端框架Vue, React和Angular都给了相应的解决方案, SSR解决了SEO问题并且加快了应用尤其是单页面应用的首屏加载速度,而对于某些内容不常变化的静态页面,我们还能采用静态网站生成(Static Site Generation/SSG)的方式,让网页在打包的过程中就生成好,用户请求时直接将网页传给客户端。然而在实际应用中,我们往往要采用SSG和SSR相结合的方式来灵活处理,于是next.js便首次提出了增量式的网站渲染(Incremental Site Rendering/ISR)。

这篇文章主要会介绍如何在Next.js中实现SSR, SSG以及ISR,SSR和SSG相信很多人都已经非常熟悉,而且这两个概念也很好理解,前者是在用户请求时服务端生成html,后者是在项目打包时生成html,也叫打包时的预渲染(pre-render at build time)。

但SSG往往会有这样的问题:内容变了需要重新打包项目发布,否则用户会看到过时的数据,不仅如此,如果我有多个产品需要做推广,我们的路由往往是.../products/productId,这时如果有一个新增的产品然后发现服务器上没有对应的静态文件呢,再重新打包吗?或许我们可以采用服务端渲染的方式,但ISR更是一个不错的选择。

ISR即增量式的网站渲染,顾名思义,服务端发现找不到productId对应的html文件,那么它会认为其为增量数据,并立即对其进行静态网页生成, 这样下次客户端请求这个产品时就可以直接返回这个新的html了。

Next.js对这三种渲染方式都有对应的解决方案,主要是用到这三个方法:getStaticProps(), getStaticPaths(), getServerSideProps()。这三个方法都只会在服务端运行,这意味着你可以尽情使用node.js中的所有模块。

二、通过getServerSideProps()实现SSR

如果需要服务端在用户请求时去生成页面,则可以使用getServerSideProps()方法, 该方法入参为context对象,该对象包含了:

  • params, 动态路由的信息
  • reqres, http请求头信息与响应对象
  • query, http查询参数
  • ...等等

该方法只需要返回传到页面组件的参数props, 或notFoundRedirect。比如下面要服务端渲染一个文章列表页:

export async function getServerSideProps({params, req, res, query...}){ 
    const res = await fetch(`http://localhost:3000/api/articles`); 
    return { 
        props: { 
            articles: res.json() // your data } 
            // notFound: true 
            // redirect: {...} 
        } 
    } 
// 页面组件可从props中拿到articles 
export const ArticleList = ({articles}) => { ... }

如果返回对象里notFound为true,表示资源未找到,前端将会返回404页面。如果返回了redirect信息则表示该页面需要被重定向到其他页面。

三、通过getStaticProps()实现SSG

如果需要在项目打包的时候生成页面,则可以使用getStaticProps()方法,该方法的入参和出参和getServerSideProps方法差不多,但入参没有req和res,因为该方法主要在项目打包时运行。

export async function getStaticProps() { 
    ...... 
    return { 
        props:{ 
        articles: await res.json() } 
        // notFound: true 
        // redirect: {...} } 
    } 
export const ArticleList = ({articles}) => { ... }

四、通过getStaticPaths()实现动态路由预渲染与ISR

现在如果要对多个文章详情进行预渲染,其实就是对动态路由页面进行预渲染,就需要使用getStaticPaths()方法。

export async function getStaticProps({params}) { 
    ...... const res = await fetch(`http://localhost:3000/api/articles/${params.id}`); 
    return { 
        props:{ 
            articles: 
            await res.json() 
        } 
     } 
} 

export async function getStaticPaths({params}) { 
    return { 
        paths: [ 
            { params: {id: `1`} }, 
            { params: {id: `2`} } 
        ], 
        fallback: false 
    } 
} 
export const Article = ({article}) => { ... }

getStaticPaths主要返回2个属性,一个是设置的路径规则paths,比如示例中预渲染了id为1和2的两篇文章,只有少量文章的话我们完全可以通过id写死的这种形式,但有很多篇并且id不确定呢,这就需要设置fallback参数。

a. fallback设为false

fallback设为false的话,那就是完全SSG的形式,只根据paths去预渲染页面,如果有规则外的paths,前端会显示404页面。

b. fallback设为true

fallback可翻译为应变计划,退路,意思就是当遇到匹配不了的paths时,也就是服务器上并没有请求对应的已生成好的页面时,服务器会立即去生成一份,而不是返回404页面,但这时候需要在页面组件里设置一个loading反馈。

export async function getStaticPaths({params}) { 
    return { 
        paths: [...] 
        fallback: true 
    } 
} 
export const Article = ({article}) => { 
    const router = useRouter(); 
    if(router.isFallback){ return <>Loading...</> } 
    ...... 
}

loading实际上表示服务器正在生成html,在项目打包时会预渲染paths规则里的页面,而在项目运行时遇到规则外的请求,服务端会立即去生成一份。

c.  fallback设为blocking

此时遇到新的path服务端也是会去生成一份新的html的,但没有fallback状态,也就是服务端生成html时前端没有加载状态,可能会造成用户点击后过一两秒才能跳转,这也是为啥叫blocking(阻塞, 堵塞的意思)。

五. getStaticProps()中的Revalidate

但到目前为止还有一个问题,就是这些预渲染的页面的内容是不会被更新的,除非再去打包发布,这对一些数据经常更新的网站是不能接受的,这时我们可以通过设置getStaticProps方法返回对象中的属性Revalidate。

export async function getStaticProps({params}) { 
    ...... 
    return { 
        props: {...} 
        revalidate: 10 // seconds 
    } 
}

revalidate表示验证html缓存的周期, 以上代码如果10秒后用户请求了该页面,服务器会去验证缓存与实际数据是否一致,不一致则说明数据有改动,那服务器会根据新数据重新生成一份html, 下一次的请求就会返回这个新的html。其实每次比对的也只是通过接口拿到的json数据。

revalidate默认值是false, 也就是一旦预渲染好了,就不会被改变了。

总结

所以next.js提出的增量式网站渲染(ISR)就是允许在项目打包后也就是项目运行时去创建或更新服务器上的html文件,这样客户端就可以请求服务器上上千万的静态页面,而且这些页面的数据能够被更新。

  • 如果需要预渲染动态路由的页面,使用getStaticPaths()方法。
  • 如果动态路由可能会有上千万的页面,可以设置getStaticPaths()fallback为true或blocking
  • 如果页面数据经常更新,设置getStaticProps()的revalidate的值为更新的频率