• Features
  • Pricing
Get Started

Next.jsAstroReact RouterNuxtSvelteKitManual

Next.js

Server-side rendering with the Next.js App Router for a simple Paragraph CMS blog.

This quickstart uses the Next.js App Router and walks through the core setup for fetching and rendering Paragraph CMS content with server-side rendering (SSR). SSR keeps the API key and content requests on the server, and is the recommended delivery model for Paragraph CMS.

Want a ready-made project instead? Use the paragraphcms/nextjs-starter, which ships with the same integration pattern already wired into a blog.

Install the Package

Add the Paragraph CMS client and React renderer to your Next.js app:

npm install @paragraphcms/client @paragraphcms/parser-react
pnpm add @paragraphcms/client @paragraphcms/parser-react
yarn add @paragraphcms/client @paragraphcms/parser-react
bun add @paragraphcms/client @paragraphcms/parser-react

Set Environment Variables

Create a .env file in the project root:

.env
PARAGRAPH_API_KEY=your_api_key

Create a Paragraph CMS Client

Create paragraph.config.ts and initialize a shared client:

paragraph.config.ts
import { Client } from "@paragraphcms/client";

const apiKey = process.env.PARAGRAPH_API_KEY;

if (!apiKey) {
  throw new Error("PARAGRAPH_API_KEY environment variable is not set");
}

export const client = new Client({ apiKey });

Create Example Components

Create two very simple components that we will render in the next steps:

components/blog/blog.tsx
import type { PageSummaryWithSlug } from "@paragraphcms/client";
import Link from "next/link";

export function Blog({ posts }: { posts: PageSummaryWithSlug[] }) {
  return (
    <main>
      <h1>Blog</h1>

      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link href={`/blog/${post.slug}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
    </main>
  );
}
components/blog/post.tsx
import type { PageWithSlug } from "@paragraphcms/client";
import { ParagraphContent } from "@paragraphcms/parser-react";

export function Post({ page }: { page: PageWithSlug }) {
  return (
    <main>
      <h1>{page.title}</h1>
      <ParagraphContent content={page.content} />
    </main>
  );
}

Server-render /blog

Build the /blog index route with client.pages.list(). Setting requiredSlug: true narrows the result to PageSummaryWithSlug[], so post.slug is safe to use directly.

app/blog/page.tsx
import { Blog } from "@/components/blog/blog";
import { client } from "@/paragraph.config";

export default async function Page() {
  const { data, error } = await client.pages.list({
    requiredSlug: true,
  });

  if (error) {
    throw error;
  }

  return <Blog posts={data} />;
}

By default, client.pages.list() returns published pages only. If you want to include unpublished pages too or limit the query to a single collection, pass additional parameters under the same call:

client.pages.list({
  requiredSlug: true,
  published: false,
  collection: "blog",
});

Server-render /blog/[slug]

Render the requested /blog/[slug] route on the server and resolve the page with client.page.getBySlug().

app/blog/[slug]/page.tsx
import { Post } from "@/components/blog/post";
import { client } from "@/paragraph.config";

export const dynamic = "force-dynamic";

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const { data, error } = await client.page.getBySlug(slug);

  if (error) {
    throw error;
  }

  return <Post page={data} />;
}

dynamic = "force-dynamic" makes the SSR model explicit. Configure response caching for the platform where the app runs.

Once this is wired, the rest of the page can be standard Next.js UI around the rendered Paragraph content.

Concepts

How workspaces, teams, collections, pages, labels, locales, and media fit together in Paragraph CMS.

Astro

Server-side rendering in Astro for a simple Paragraph CMS blog.

On this page

Install the PackageSet Environment VariablesCreate a Paragraph CMS ClientCreate Example ComponentsServer-render /blogServer-render /blog/[slug]