Display a view counter on your blog with React Server Components

Posted on April 24, 2023·

I remember the time it was so cool to have a view counter on my personal website. A perfect beginner project for web developers, just before the more complicated guest book. Now it isn’t as common as it used to be, but I wanted to add such a view counter on my blog, for each post. Here is a short tutorial in case you want to add one on yours, combining React Server Components, Streaming and Suspense in a Next.js application.

Instead of building my own counter storing the data somewhere, I decided to rely on Plausible, the tool I’m using for analytics. Its API offers an easy way to get the view count for a given page, which is exactly what I need.

Here is the function I wrote responsible for getting the view count for any page of my website:

// lib/plausible.ts
export async function getCountForPage(page: string): Promise<number> {
  const siteId = 'scastiel.dev'
  const period = '12mo'
  const filters = encodeURIComponent(`event:page==${page}`)

  const params = `site_id=${siteId}&period=${period}&filters=${filters}`
  const url = `https://plausible.io/api/v1/stats/aggregate?${params}`
  const headers = { Authorization: `Bearer ${process.env.PLAUSIBLE_API_KEY!}` }
  const res = await fetch(url, { headers })

  return (await res.json()).results.visitors.value
}

Now, where should I call this function?

Classic client-server communication with React

Classicly, this function is called in a serverless function, e.g. /api/count?path=..., and we can call this serverless function from a React component (or a custom hook). Here is cool way to do it using the great useSwr hook:

import useSwr from 'swr'

const fetcher = (url: string) => fetch(url).then((res) => res.json())

function ViewCount({ page }: { page: string }) {
  const { data } = useSwr(`/${slug}/stats`, fetcher)
  if (!data) return null
  return <span>{data.count} views</span>
}

This works pretty well. The blog post is rendered by the browser, then an HTTP request is sent to the server, returning the view count, which is then rendered on the page.

BrowserServerPlausiblefetch('/api/count'){ count: 123 }GET /my-post<Post>...</Post>Render #1Render #2

This way of doing is the one I’m using on this blog for individual post. What follows in this post can only by used in pages using Server-Side Rendering, not the ones statically generated (and all my post pages are statically generated). The solution I present is used on the Articles page to display views count for each post.

But React Server Components offer an elegant pattern we can use. What if the view count was sent with the request returning the blog post itself?

Using React Server Components and SSR

With Server-Side Rendering (SSR), server components are rendered only on the server, which gives them the ability to perform async operations for instance. We can create a PageViews component calling the getCountForPage function, and then rendering the count.

// components/PageViews.tsx
import { getCountForPage } from '@/lib/plausible'

export async function PageViews({ slug }: { slug: string }) {
  const count = await getCountForPage(`/${slug}`)
  return <span title="Views in the last year">{count} views</span>
}

Now we can use this PageViews component anywhere, like in the page.tsx file responsible for rendering a blog post:

// app/[slug]/page.tsx
import { PageViews } from '@/components/PageViews.tsx'

export default function PostPage({
  params: { slug },
}: {
  params: { slug: string }
}) {
  // ...
  return (
    <div>
      {/* @ts-expect-error */}
      <PageViews slug={slug} />
    </div>
  )
}

While already supported by React, TypeScript is still a bit relunctant to using async components in JSX (hence the @ts-expect-error), but it’s coming with TypeScript 5.1 at the time I’m writing this post 😉.

Now you might think that will not be great if fetching the view count from Plausible takes some time (like one second), as it would block the rest of the page from being sent to the browser.

BrowserServerPlausibleGET /my-postWaiting… ⏳Render #1<Post>...</Post>

This is where Streaming comes handful!

Leveraging Streaming to send intermediate results

Streaming allows the server to send a first version of the page, keep the connection open, then send a second version (with our view count) a bit later.

And as implementing streaming might sound scary, it turns out React does all the work for us! All we have to do is wrap our <PageViews /> in a <Suspense /> block:

return (
  <div>
    <Suspense fallback={<span>Loading…</span>}>
      {/* @ts-expect-error */}
      <PageViews slug={slug} />
    </Suspense>
  </div>
)

Now, React won’t wait for PageViews to be rendered. On the server, it will first render a first version using the provided fallback and send it to the browser. Then, when the PageViews part is ready (i.e. when we got our view count), it will send a second version to the browser.

BrowserServerPlausibleGET /my-post<Post>⏳</Post>Render #1Render #2<Post>👁️6</Post>

From the user point of view, this solution is very similar to the first one. But from ours, the code is easier to read and maintain, as we delegate a lot to React and to the server.

We can obviously do much more with React Server Components and Streaming, but I like this tiny use case, as it’s the perfect opportunity to get familiar with these new features.

Do you have any cool use case to share?


Some resources to know more:


Check my latest articles

  • 📄 13 tips for better Pull Requests and Code Review (October 17, 2023)
    Would you like to become better at crafting pull requests and reviewing code? Here are the 13 tips from my latest book that you can use in your daily developer activity.
  • 📄 The simplest example to understand Server Actions in Next.js (August 3, 2023)
    Server Actions are a new feature in Next.js. The first time I heard about them, they didn’t seem very intuitive to me. Now that I’m a bit more used to them, let me contribute to making them easier to understand.
  • 📄 Intro to React Server Components and Actions with Next.js (July 3, 2023)
    React is living something these days. Although it was created as a client UI library, it can now be used to generate almost everything from the server. And we get a lot from this change, especially when coupled with Next.js. Let’s use Server Components and Actions to build something fun: a guestbook.