Skip to main content

Next.js Hybrid Rendering

Free2020-12-13#Front-End#Mind#hybrid ssr#混合渲染模式#SSG vs SSR#CSR vs SSR#混用SSR和CSR

SSG has the best performance, SSR has the widest application scope, CSR has the best navigation experience. Must we choose between the three? No, I want them all

Preface

In the React ecosystem, Next.js probably has the best SSR support, but SSR is not Next.js's entirety, just one of its pre-rendering supports:

  • SSG (Static Site Generation/Static Generation): Static generation, generates static HTML at compile time

  • SSR (Server-Side Rendering): Server-side rendering, dynamically generates HTML when user requests arrive

Pre-render page content before CSR starts through various methods, thereby accelerating first-screen performance while meeting SEO needs, this is precisely Next.js's core feature

Not only that, Next.js also provides mixed-use support, able to combine different rendering modes together, blending and complementing each other, for example:

  • ISR (Incremental Static Regeneration): Incremental static regeneration, periodically regenerates static HTML at runtime

  • SSG fallback to SSR: When missing pre-generated static HTML, immediately perform SSR

  • SSR with static cache: After SSR completes, cache the result, next time hit static cache and return directly (equivalent to SSG)

  • SSG combined with CSR: Generate static parts at compile time (page frame), CSR fills dynamic parts (page content)

  • SSR linked with CSR: Direct URL access takes faster SSR, SPA navigation takes better-experienced CSR

These nuanced hybrid rendering supports allow various rendering modes to fully leverage their advantages, also making Next.js even more attractive

SSG + SSR

SSG is equivalent to moving SSR's rendering process forward to compile time, thereby optimizing away this part of time consumption, achieving excellent page loading performance. But there's also an obvious defect—can only be used to render static content, making an originally powerful solution hard to find use. So, is there a way to expand its application scenarios?

Yes. The key lies in how to understand "static", static and dynamic actually describe content change frequency, almost (forever) unchanging, or very low frequency changing content, we call it static content. So as long as we find ways to cope with content changes, it's possible to expand SSG's application scenarios from frequently unchanging "static content" to infrequently changing "dynamic content"

In extreme cases, "infrequently changing" is equivalent to "not changing every time", that is to say, except for real-time/personalized etc. content that changes dynamically every moment, the rest of scenarios can use SSG, of course, premise is to guarantee content can update and take effect at needed frequency. Content update is actually re-SSG, so only missing an update timing...

Another not-so-obvious limitation is the quantity of static content, because rendering work must be completed entirely at compile time, if there are 1 million pieces of static data, need to compile and generate 1 million HTML files, compiling once may take several days... Compilation cost (whether time/machine) will continuously increase with content quantity, this is an innate problem of SSG rendering mode, looks unsolvable. Unless, don't generate full pages at compile time...

And SSR oriented to user requests happens to be able to provide suitable update timing, while as downstream of compilation, SSR has opportunity to catch missed fish. Thus, SSG and SSR hit it off immediately, SSG only compiles and generates a small subset of hot pages, the rest are generated through SSR at runtime. When user requests arrive, decide whether to go through SSR to regenerate or use last generated product based on whether content needs updating:

Instead, you may statically generate a small subset of pages and use fallback: true for the rest. When someone requests a page that's not generated yet, the user will see the page with a loading indicator. Shortly after, getStaticProps finishes and the page will be rendered with the requested data. From now on, everyone who requests the same page will get the statically pre-rendered page.

Inspired by stale-while-revalidate, background regeneration ensures traffic is served uninterruptedly, always from static storage, and the newly built page is pushed only after it's done generating.

In this way, SSG expands application scenarios (high-frequency changing content, massive content that can't finish compiling), SSR gains performance advantages (static cache):

This ensures that users always have a fast experience while preserving fast builds and the benefits of Static Generation.

P.S. For more information about SSG and SSR combination, see When is fallback: true useful?, Incremental Static Regeneration

SSG + CSR

Compared to SSR, SSG has lower cost, locally compile and generate static HTML, host to web server or CDN to enjoy loading performance improvement from pre-rendering, no high machine cost of application server, also no need to worry about SSR online service availability and operations work

Using SSR to expand SSG's application scenarios must consider the accompanying cost problem, so, is there a lower cost method?

Yes, but there needs to be some compromise on experience. Since SSG is good at rendering static content, might as well separate static and dynamic content on pages, hand static parts of page to SSG to compile and generate, the rest dynamic parts still fill through CSR:

First, immediately show the page without data. Parts of the page can be pre-rendered using Static Generation. You can show loading states for missing data.

Then, fetch the data on the client side and display it when ready.

SSG combined with CSR, both shortens page loading white screen time, and avoids SSR's extra cost. However, the flaw is loading experience is not as good as pure SSG, after all (what users may care more about) dynamic content needs secondary rendering on client side to present, not like SSG can present complete content at once. Therefore, this way brings more experience improvement, users perceive page loading becomes faster, considered a progressive rendering mode

P.S. For more information about SSG and CSR combination, see Fetching data on the client side

SSR + CSR

Among SSG, SSR, CSR three combining pairwise, the most intriguing may be this third kind—SSR combined with CSR

hydrate doesn't count, is there still a combination point between SSR and CSR?

Of course there is. SSR can effectively shorten white screen time during page loading process, while provide smooth experience of presenting page content completely at once, compared to this, CSR rendering performance depends on client environment, data request lag etc. shortcomings become infinitely large, so large they cover up CSR's highlight advantages:

  • Load content without refresh

  • Can pre-load based on user behavior

These advantages indeed don't show up during first-screen loading process, so looking at page loading performance alone, SSR completely beats CSR, choose one between the two is enough, no need to combine. However, if we elevate perspective to user operation's full flow, we find CSR and SSR can combine perfectly in a very harmonious way:

  • First-screen loading goes through SSR: Whether user directly accesses homepage or second-level, third-level pages through URL, SSR can present pages at fastest speed

  • In-site navigation goes through CSR: Subsequent page navigation in interaction operations, seamlessly load new content through CSR, even can predict user behavior to pre-load target page content in advance

That is, hand first-screen loading work to faster SSR, let CSR show its skills during interaction process:

When you request this page directly, getServerSideProps runs at the request time, and this page will be pre-rendered with the returned props.

When you request this page on client-side page transitions through next/link or next/router, Next.js sends an API request to the server, which runs getServerSideProps. It'll return JSON that contains the result of running getServerSideProps, and the JSON will be used to render the page. All this work will be handled automatically by Next.js, so you don't need to do anything extra as long as you have getServerSideProps defined.

Next.js not only provides built-in support for this combination method, but also can automatically pre-load in-site links in visible area:

prefetch - Prefetch the page in the background. Defaults to true. Any that is in the viewport (initially or through scroll) will be preloaded. Prefetch can be disabled by passing prefetch={false}.

P.S. For more information about SSR combined with CSR, see Only runs on server-side

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment