• Features
  • Pricing
Get Started

Next.jsAstroReact RouterNuxtSvelteKitManual

Astro

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

This quickstart uses Astro's file-based routing and walks through the core setup for fetching and rendering Paragraph CMS content with server-side rendering (SSR). SSR is the recommended delivery model for Paragraph CMS: content requests and the API key remain on the server.

Want a ready-made project instead? Use the paragraphcms/astro-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 Astro app. If you are not already using React components in Astro, install the React integration too:

npm install @paragraphcms/client @paragraphcms/parser-react react react-dom
npm install -D @astrojs/react @astrojs/node
pnpm add @paragraphcms/client @paragraphcms/parser-react react react-dom
pnpm add -D @astrojs/react @astrojs/node
yarn add @paragraphcms/client @paragraphcms/parser-react react react-dom
yarn add -D @astrojs/react @astrojs/node
bun add @paragraphcms/client @paragraphcms/parser-react react react-dom
bun add -d @astrojs/react @astrojs/node

Register the React integration and a server adapter in astro.config.mjs:

astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import react from "@astrojs/react";

export default defineConfig({
  adapter: node({ mode: "standalone" }),
  integrations: [react()],
  output: "server",
});

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. In Astro, server-side environment variables are read through import.meta.env:

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

const apiKey = import.meta.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:

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

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

      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <a href={`/blog/${post.slug}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </main>
  );
}
src/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.

src/pages/blog/index.astro
---
import { Blog } from "../../components/blog/blog";
import { client } from "../../../paragraph.config";

const { data, error } = await client.pages.list({
  requiredSlug: true,
});

if (error) {
  throw error;
}
---

<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]

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

src/pages/blog/[slug].astro
---
import { Post } from "../../components/blog/post";
import { client } from "../../../paragraph.config";

const { data: page, error } = await client.page.getBySlug(Astro.params.slug!);

if (error) {
  throw error;
}
---

<Post page={page} />

Choose the adapter for your host when you deploy; the example uses the Node adapter to make SSR explicit during local development.

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

Next.js

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

React Router

SSR with React Router framework-mode loaders 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]