Search code examples
javascriptnext.jsswr

Static generation and SWR in single page


I'm building a blog website in Next.js, the API for the blog is from some headless CMS.

In a page I want to do the following:

  1. List some blogs.
  2. Set of buttons available, based on each button click different set of blogs are loading (should replace the blogs in #1).

Since SEO is needed I'm pretty confused to use which approach should I choose.

What I thinking that I generate the initial list with getStaticProps (Static Generation), and after loading I want to replace the blogs based on user action (button click).

But I'm confused, is it possible to use static generation and SWR in single page?

Here is my implementation.

pages/index.js

export async function getStaticProps() {
    const resPosts  = await fetch(`${process.env.API_BASE_URL}posts?per_page=4&&_embed`)
    const posts = await resPosts.json()
    return {
        props: {
            posts
        },
        revalidate:10
      }
}

export default function Home({posts}) {
  return (
    <div> 
      //pass data to FeaturedBlogs component (Components/featuredBlogs.js)
      <FeaturedBlogs categories={categories} posts={posts} />
    </div>
  )    
}

Components/featuredBlogs.js

const FeaturedBlogs = ({posts }) => {            
    return (
        <div className={`${blogStyles.feature_blogs_wrap}`}>
            //need to load the below blogs based on button click
            <button onClick={handleClick('health')}>Health</button>
            <button onClick={handleClick('latest')}>Latest</button>
            //listing blogs
            {posts.map((item ) => ( 
                <Link  key={item.id} href="/blog/view" passHref={true}>
                    <section>
                        <Image alt="blog_img" src={item._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url} width="200" height="200" />
                        <div className={`${blogStyles.feature_blogs_content}`}>
                            <div className={`${blogStyles.feature_blogs_label}`}>
                                <span>{item._embedded['wp:term'][0][0].name}</span>
                            </div>
                            <p>{item.title.rendered}</p>
                            <div className={`${blogStyles.feature_blogs_author}`}>
                                <Image alt="author" src={item._embedded.author[0].avatar_urls[48]}  width="200" height="200" />
                                <span>{item._embedded.author[0].name}</span>
                            </div>
                        </div>
                    </section>
                </Link>
            ))}
        </div>
    )
}
            
const handleClick = (id) =>  {
    //console.log(id)  
}       

What I need is to load the blogs in handleClick event, but the problem is this will not work since it's generated from the server at build time.


Solution

  • In the FeaturedBlogs component, you can create a state variable to keep track when a new category is selected on the client-side.

    const [category, setCategory] = useState()
    

    You can then make useSWR conditionally fetch data based on the value of this category variable.

    const { data, loading } = useSWR(category ? [category] : null, fetcher)
    

    The fetcher function would have the logic to fetch the posts for a given category.

    const fetcher = async (category) => {
        const response = await fetch(/* Endpoint to get posts for given category */)
        return await response.json()
    }
    

    With this in place, you can have the component render the posts retrieved in getStaticProps as a default, when category is not set. This would happen on the initial render of the page. However, when a button is clicked, and category gets set, that category's data will be fetched and rendered instead.

    Here's the full code of a modified version of your original component.

    // Components/featuredBlogs.js
    
    const fetcher = async (category) => {
        const response = await fetch(/* Endpoint to get posts for given category */)
        return await response.json()
    }
    
    const FeaturedBlogs = ({ posts }) => {
        // Add state variable to keep track of the selected category
        const [category, setCategory] = useState()
        // Fetch posts from category only if `category` is set
        const { data, loading } = useSWR(category ? [category] : null, fetcher)
    
        const handleClick = (cat) => () => {
            setCategory(cat)
        }
        
        // If `category` is set render data with post for given category, otherwise render all posts from `getStaticProps`
        const itemsToRender = category ? data : posts
    
        return (
            <div className={blogStyles.feature_blogs_wrap}>
                <button onClick={handleClick('health')}>Health</button>
                <button onClick={handleClick('latest')}>Latest</button>
                {loading && <div>Loading...</div>}
                {!!itemsToRender?.length && itemsToRender.map((item) => (
                    <!-- Render items here -->
                ))}
            </div>
        )
    }