Rendering Pattern
Incremental Static Generation
Static Generation (SSG) addresses most of the concerns of SSR and CSR but is suitable for rendering mostly static content. It poses limitations when the content to be rendered is dynamic or changing frequently.
Think of a growing blog with multiple posts. You wouldn’t possibly want to rebuild and redeploy the site just because you want to correct a typo in one of the posts. Similarly, one new blog post should also not require a rebuild for all the existing pages. Thus, SSG on its own is not enough for rendering large websites or applications.
The Incremental Static Generation (iSSG) pattern was introduced as an upgrade to SSG, to help solve the dynamic data problem and help static sites scale for large amounts of frequently changing data. iSSG allows you to update existing pages and add new ones by pre-rendering a subset of pages in the background even while fresh requests for pages are coming in.
Sample Code
iSSG works on two fronts to incrementally introduce updates to an existing static site after it has been built.
- Allows addition of new pages
- Allows updates to existing pages also known as Incremental Static “Re”generation
Adding New pages
The lazy loading concept is used to include new pages on the website after the build. This means that the new page is generated immediately on the first request. While the generation takes place, a fallback page or a loading indicator can be shown to the user on the front-end. Compare this to the SSG scenario discussed earlier for individual details page per product. The 404 error page was shown here as a fallback for non-existent pages.
Let us now look at the Next.js code required for lazy-loading the non-existent page with iSSG.
1// In getStaticPaths(), you need to return the list of2// ids of product pages (/products/[id]) that you'd3// like to pre-render at build time. To do so,4// you can fetch all products from a database.5export async function getStaticPaths() {6 const products = await getProductsFromDatabase();78 const paths = products.map((product) => ({9 params: { id: product.id }10 }));111213 // fallback: true means that the missing pages14 // will not 404, and instead can render a fallback.15 return { paths, fallback: true };16}1718// params will contain the id for each generated page.19export async function getStaticProps({ params }) {20 return {21 props: {22 product: await getProductFromDatabase(params.id)23 }24 }25}262728export default function Product({ product }) {29 const router = useRouter();3031 if (router.isFallback) {32 return <div>Loading...</div>;33 }3435 // Render product36}
Here, we have used fallback: true
. Now if the page corresponding to a specific
product is unavailable, we show a fallback version of the page, eg., a loading
indicator as shown in the Product function above. Meanwhile, Next.js will
generate the page in the background. Once it is generated, it will be cached and
shown instead of the fallback page. The cached version of the page will now be
shown to any subsequent visitors immediately upon request. For both new and
existing pages, we can set an expiration time for when Next.js should revalidate
and update it. This can be achieved by using the revalidate property as shown in
the following section.
Update Existing pages
To re-render an existing page, a suitable timeout is defined for the page. This will ensure that the page is revalidated whenever the defined timeout period has elapsed. The timeout could be set to as low as 1 second. The user will continue to see the previous version of the page, till the page has finished revalidation. Thus, iSSG uses the stale-while-revalidate strategy where the user receives the cached or stale version while the revalidation takes place. The revalidation takes place completely in the background without the need for a full rebuild.
Let us go back to the example for generating a static listing page for products based on the data in the database. To make it serve a relatively dynamic list of products, we will include the code to set the timeout for rebuilding the page. This is what the code will look like after including the timeout.
1// This function runs at build time on the build server2export async function getStaticProps() {3 return {4 props: {5 products: await getProductsFromDatabase(),6 revalidate: 60, // This will force the page to revalidate after 60 seconds7 }8 }9}1011// The page component receives products prop from getStaticProps at build time12export default function Products({ products }) {13 return (14 <>15 <h1>Products</h1>16 <ul>17 {products.map((product) => (18 <li key={product.id}>{product.name}</li>19 ))}20 </ul>21 </>22 )23}
The code to revalidate the page after 60 seconds is included in the
getStaticProps()
function. When a request comes in the available static page
is served first. Every one minute the static page gets refreshed in the
background with new data. Once generated, the new version of the static file
becomes available and will be served for any new requests in the subsequent
minute. This feature is available in Next.js 9.5 and above.
Advantages
iSSG provides all the advantages of SSG and then some more. The following list covers them in detail.
-
Dynamic data: The first advantage is obviously why iSSG was envisioned. Its ability to support dynamic data without a need to rebuild the site.
-
Speed: iSSG is at least as fast as SSG because data retrieval and rendering still takes place in the background. There is little processing required on the client or the server.
-
Availability: A fairly recent version of any page will always be available online for users to access. Even if the regeneration fails in the background, the old version remains unaltered.
-
Consistent: As the regeneration takes place on the server one page at a time, the load on the database and the backend is low and performance is consistent. As a result, there are no spikes in latency.
-
Ease of Distribution: Just like SSG sites, iSSG sites can also be distributed through a network of CDN’s used to serve pre-rendered web pages.