3 min read

SSR and CSR in Next.js

Introduction

In a recent project, I was tasked with building an AnalyticsPage for a URL shortener service. The page needed to display various statistics about URL usage, including total clicks, total links, and average clicks per link, along with a detailed list of recent links. This data was required to update in real-time to give users a current view of their analytics.

From the start, I knew this page would have to balance server-side rendering (SSR) for performance and SEO with client-side rendering (CSR) for real-time interactivity. Let’s explore how I arrived at this solution.

SSR vs CSR

Initially, I wasn’t sure whether to use SSR or CSR for the entire page, so I dove deeper into their differences:

  • Server-Side Rendering (SSR)

    • SSR renders the page on the server and sends the fully rendered HTML to the client.
    • This approach is favorable for initial load performance and SEO since crawlers can index the pre-rendered content.
  • Client-Side Rendering (CSR)

    • CSR renders content on the client after the initial HTML has loaded, which is useful for dynamic and interactive components.
    • CSR allows real-time updates, especially when fetching user-specific data.

I realized the best approach was to use both SSR and CSR. SSR for the initial static content (like the page title) and CSR for the dynamic parts (like the table and content, which depend on user data).

Client-Side and Server-Side Components

Next.js makes it easy to mix SSR and CSR by marking components with "use client". Here’s how I structured the page:

function AnalyticsPage() {
return (
<div>
<h1>Analytics Overview</h1> {/* Server-side rendered title */}
<Table /> {/* Client-side table */}
<Content /> {/* Client-side content */}
</div>
);
}

By using the "use client" directive, I ensured that Table and Content would be hydrated on the client-side after the initial HTML is served by the server.

Request Flow

The next challenge was designing the request flow to fetch user-specific analytics data in real-time while keeping the load on the server manageable. I decided to use SWR (Stale-While-Revalidate) for client-side data fetching with a refresh interval for real-time updates. Here’s how I structured the request flow:

  • SWR Hook for real-time data fetching:
export function useAnalyticsOverviewData() {
const { data, isLoading, isError } = useSWR(
'/api/analytics',
getAnalytics,
{ refreshInterval: 3000 }
);
return { data, isLoading, isError };
}
  • Server-side action for fetching the analytics data:
export const getAnalytics = cache(async (urlId) => {
const sessionId = cookies().get("url_shortener_gh_session");
if (!sessionId) throw new Error("Not authenticated");
const response = await fetch(`/api/analytics/url/${urlId}`, {
headers: { Cookie: `url_shortener_gh_session=${sessionId.value}` }
});
if (!response.ok) throw new Error("Failed to fetch analytics data");
return await response.json();
});

Flow:

  • When the page loads, SWR triggers the getAnalytics function to fetch the data.
  • The session cookie is used to authenticate the request.
  • The data is returned to the AnalyticsPage and rendered dynamically.
  • SWR’s refreshInterval option ensures the data is refreshed every 3 seconds to keep it real-time.

Key Takeaway

  • SSR for SEO and Initial Load: The static parts of the page (like the title) are server-rendered, making the page load quickly and ensuring good SEO.
  • CSR for Real-Time Data: User-specific data is handled on the client-side with SWR, ensuring the user sees up-to-date information.
  • Balanced Performance: By using SWR for data fetching every 3 seconds, I achieved real-time updates while keeping the server load manageable.

Conclusion

This project taught me how to effectively combine SSR and CSR in Next.js to create a real-time, user-specific analytics dashboard. Using SWR for client-side fetching and Next.js’ server-side capabilities, I was able to design a secure, scalable, and high-performance solution.