+2

Combining client side and server side rendering on same page using next.js

We can easily create Client rendered pages (CSR) and Static generated pages (SG) using Next.js. But, often times, you'll want your page to use a combination of those different rendering methods. Like, you want your static page to be generated with some data from api, then once the users browser has hydrated the static page, you want CSR to kick in. There is one way we can use this, by using the excellent SWR plugin from Vercel (`creators of Next.js). Let's have a quick look about it in this article.

First, let's create a next app by running

npx create-next-app blog-swr-demo -y

Now we need to create a mock api to serve us articles/posts. Let's create a new file in pages/posts.js and paste in

export default (req, res) => {
    res.status(200).json({ 
        posts: [
            {title: "A", content: "asdf"},
            {title: "B", content: "qwert"},
            {title: "C", content: "zxcv"},
            {title: "D", content: "mnopq"},
            {title: "E", content: "aeiou"}
        ]
    })
  }

Now, we'll install the SWR plugin

npm i swr

And create our blog component and also import the useSWR hook from the plugin we just installed

import useSWR from "swr"


export default function Blog() {
  return (
    <div>
        Demo blog
        {
            posts.map((post) =>
                <h4>{post.title}</h4>
                <p>{post.content}</p>
            )
        }
    </div>
  )
}

Now, let's render the blog posts using the useSWR hook

import useSWR from "swr"

async function fetcherFunc(url) {
    const res = await fetch(url);
    return res.json();
}

export default function Blog() {
  const url = "http://localhost:3000/api/posts";
  const {data, error} = useSWR(url, fetcherFunc);
  if (!data) return <div>fetching</div>
  
  const {posts} = data;

  return (
    <div>
        Demo blog
        {
            posts.map((post) =>
                <div>
                    <h4>Article: {post.title}</h4>
                    <p>{post.content}</p>
                    <br/>
                </div>
            )
        }
    </div>
  )
}

Now let's create the static generated file for our page, for this we'll modify our code like

export default function Blog(props) {   // <<<<<<< changed here
//   const url = "http://localhost:3000/api/posts";
//   const {data, error} = useSWR(url, fetcherFunc);
//   if (!data) return <div>fetching</div>

  const {posts} = props; // <<<<<<<< Also here

  return (
    <div>
        Demo blog
        {
            posts.map((post) =>
                <div>
                    <h4>Article: {post.title}</h4>
                    <p>{post.content}</p>
                    <br/>
                </div>
            )
        }
    </div>
  )
}

export async function getStaticProps(context) {
    const res = fetch("http://localhost:3000/api/posts");
    const posts = await (await res).json()

    return {
        props: {
            posts
        }
    }
}

Let's generate the page. For that run

npm run build

You'll see this after it's successfully built

> [email protected] build
> next build

info  - Creating an optimized production build  
info  - Compiled successfully
info  - Collecting page data  
info  - Generating static pages (4/4)
info  - Finalizing page optimization  

Page                                                           Size     First Load JS
┌ ○ /                                                          3.46 kB        66.9 kB
├   └ css/c50ddf22b716c7b6b76d.css                             660 B
├   /_app                                                      0 B            63.4 kB
├ ○ /404                                                       3.46 kB        66.9 kB
├ λ /api/hello                                                 0 B            63.4 kB
├ λ /api/posts                                                 0 B            63.4 kB
└ ● /blog                                                      1.96 kB        65.4 kB
+ First Load JS shared by all                                  63.4 kB
  ├ chunks/e82d01500e11e0131e78851aa17fd9f5e63d6c88.ebefd9.js  2.47 kB
  ├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.519eb7.js  11.3 kB
  ├ chunks/framework.e2fe4a.js                                 41.8 kB
  ├ chunks/main.1a4cca.js                                      6.67 kB
  ├ chunks/pages/_app.6b4817.js                                531 B
  ├ chunks/webpack.50bee0.js                                   751 B
  └ css/6e9ef204d6fd7ac61493.css                               194 B

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)

Note, the if we keep adding posts to our mock api while our blog is running, the data doesn't get refreshed. We need to reload the page to do that

Let's fix that now. For that, we will pass the revalidateOnMount option in our useSWR hook So, the final code will be

import useSWR from "swr"

async function fetcherFunc(url) {
    const res = await fetch(url);
    return res.json();
}

export default function Blog(props) {
  const url = "http://localhost:3000/api/posts";
  const {data, error} = useSWR(url, fetcherFunc, {initialData: props, revalidateOnMount: true });

  const {posts} = data;
  if (!posts) return <div>fetching</div>

  return (
    <div>
        Demo blog
        {
            posts.map((post) =>
                <div>
                    <h4>Article: {post.title}</h4>
                    <p>{post.content}</p>
                    <br/>
                </div>
            )
        }
    </div>
  )
}

export async function getStaticProps(context) {
    const res = fetch("http://localhost:3000/api/posts");
    const {posts} = await (await res).json()

    return {
        props: {
            posts
        }
    }
}

Let's check

Source code

https://github.com/Salekin-1169/blog-swr-demo

Learning Material

The official docs (obviously)


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.