Server Side Rendering (SSR)
This section provides an overview of how to implement server-side rendering (SSR) in a Next.js application using Apollo Client to fetch and display CMS data.
When you use SSR?
Server-side rendering (SSR) is particularly useful when you want to ensure that your application has the following characteristics:
- SEO Optimization: SSR allows search engines to crawl your pages more effectively, as the content is rendered on the server before being sent to the client. This is crucial for public-facing pages where SEO is important.
- Faster Initial Load: By rendering the initial HTML on the server, users can see the content more quickly, which can improve the perceived performance of your application.
- Data Fetching: SSR is beneficial when you need to fetch data from an API before rendering the page. This ensures that the data is available when the page is loaded, reducing the need for client-side fetching and improving the user experience.
- Dynamic Content: If your application has dynamic content that changes frequently, SSR can help ensure that users always see the most up-to-date information without requiring additional client-side requests.
- Improved User Experience: By serving fully rendered pages, SSR can enhance the overall user experience, especially for users on slower connections or devices.
⚠️ In other words you should use SSR on all page.tsx files that are not marked with
"use client"directive. This ensures that the page is rendered on the server, providing a fully populated HTML document to the client.
How to implement SSR in Next.js with Apollo Client
To implement server-side rendering (SSR) in a Next.js application using Apollo Client, you need to follow these steps:
- Create fetchCms.ts: This file will handle the server-side data fetching logic using Apollo Client. You need to follow the structure below to set up the Apollo Client for SSR.
// lib/fetchCms.ts
import { GET_CMS_MENU_LIST, GET_CMS_POST, GET_CMS_POSTS } from "@/graphql/queries";
import { getClient } from "./client";
import { CmsMenuList, CmsMenuListVariables } from "@/types/cms";
export async function fetchMenuList(cpId: string, kind: string) {
const client = getClient();
try {
const { data } = await client.query<{ cmsMenuList: CmsMenuList }, CmsMenuListVariables>({
query: GET_CMS_MENU_LIST,
variables: { clientPortalId: cpId, kind },
});
return data.cmsMenuList;
} catch (error) {
console.error("Error fetching Menu List:", error);
return [];
}
}
export async function fetchCmsPosts(variables: any) {
const client = getClient();
try {
const { data } = await client.query({
query: GET_CMS_POSTS,
variables,
});
return data.cmsPosts;
} catch (error) {
console.log(variables, "variables");
console.error("Error fetching CMS Posts:", error);
return [];
}
}
export async function fetchCmsPost(variables: any) {
const client = getClient();
try {
const { data } = await client.query({
query: GET_CMS_POST,
variables,
});
return data.cmsPost;
} catch (error) {
console.error("Error fetching CMS Post:", error);
return [];
}
}
- in order to use the
fetchCms.tsfile, you need to create a page that will utilize this function to fetch data on the server side. Here’s an example of how to do that:
// app/page.tsx
import { fetchCmsPosts } from "@/lib/fetchCms";
export const metadata = {
title: pageData.title,
description: pageData.description,
};
export default async function Home() {
const posts = await fetchCmsPosts({
clientPortalId: "your-client-portal-id", // Replace with your actual client portal ID
categoryId: "your-category-id", // Replace with your actual category ID
page: 1, // Adjust the page number as needed
perPage: 10, // Adjust the number of posts per page as needed
language: "en", // Replace with your desired language
});
return (
<div>
<h1>CMS Posts</h1>
<ul>
{posts.map((post) => (
<li key={post._id}>{post.title}</li>
))}
</ul>
</div>
);
}
- Here's the example of dynamic route that uses the
fetchCmsPostfunction to fetch a single CMS post based on its ID:
// app/blog/[id]/page.tsx
import { fetchCmsPost } from "@/lib/fetchCms";
export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const id = (await params).id;
// fetch post information
const post = await fetchCmsPost({
clientPortalId: "your-client-portal-id", // Replace with your actual client portal ID
id: id, // Use the dynamic ID from the URL
language: "en", // Replace with your desired language
});
return {
title: post.title,
description: post.description,
};
}
export default async function BlogPost({ params }: { params: { id: string } }) {
const post = await fetchCmsPost({
clientPortalId: "your-client-portal-id", // Replace with your actual client portal ID
id: params.id, // Use the dynamic ID from the URL
language: "en", // Replace with your desired language
});
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</div>
);
}