Getting Started
Add production-ready SEO to your React SSR app in under 5 minutes.
1Install the package
Pick your package manager and run one command:
npm install react-ssr-seo-toolkit
yarn add react-ssr-seo-toolkit
pnpm add react-ssr-seo-toolkit
Peer dependency: React >= 18.0.0 is required. Zero other dependencies.
2Recommended project structure
Here's a typical folder layout. The key idea: pages never write <html> or <head> tags — that's handled by the Document component, just like in Next.js or any modern React framework.
├── config/
│ └── seo.ts # Site-wide SEO defaults
├── components/
│ └── Document.tsx # Handles <html>, <head>, <SEOHead>, <body>
├── pages/
│ ├── HomePage.tsx # Just content + SEO config
│ ├── AboutPage.tsx # No <html> tags here!
│ └── BlogPost.tsx
├── server.tsx # Express / SSR entry point
└── package.json
This is the same pattern used by Next.js (layout.tsx), Remix (root.tsx), and React Router (Root component). Your pages only return content — the Document handles the HTML shell.
3Create a shared SEO config
This file holds defaults that every page inherits. Pages override only what they need.
import { createSEOConfig } from "react-ssr-seo-toolkit";
export const siteConfig = createSEOConfig({
titleTemplate: "%s | My Site",
description: "Default site description for SEO.",
openGraph: {
siteName: "My Site",
type: "website",
locale: "en_US",
},
twitter: {
card: "summary_large_image",
site: "@mysite",
},
});
export const SITE_URL = "https://mysite.com";titleTemplate uses %s as a placeholder. Setting title: "About" on a page renders as About | My Site.
3.5Create a Document component
The Document handles <html>, <head>, <SEOHead>, and <body> so your pages never have to. This is the same pattern used by Next.js layout.tsx, Remix root.tsx, and React Router's root component.
import { SEOHead, JsonLd } from "react-ssr-seo-toolkit";
import type { SEOConfig } from "react-ssr-seo-toolkit";
interface DocumentProps {
children: React.ReactNode;
seo: SEOConfig;
schemas?: Record<string, unknown>[];
}
export function Document({ children, seo, schemas }: DocumentProps) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{/* SEO tags are rendered here — pages don't need to */}
<SEOHead {...seo} />
{/* Render any JSON-LD schemas passed by the page */}
{schemas?.map((schema, i) => (
<JsonLd key={i} data={schema} />
))}
</head>
<body>
<nav>{/* Your shared navigation */}</nav>
<main>{children}</main>
<footer>{/* Your shared footer */}</footer>
</body>
</html>
);
}Key concept: The Document is the only file that writes <html> and <head>. Every page just provides its SEO config and content — the Document renders everything else.
4Build your first SEO page
Notice: no <html> or <head> tags here! The page just provides its SEO config and content. The Document handles the rest.
import { mergeSEOConfig, buildCanonicalUrl } from "react-ssr-seo-toolkit";
import { siteConfig, SITE_URL } from "../config/seo";
import { Document } from "../components/Document";
export function AboutPage() {
// Merge site defaults with page-specific SEO
const seo = mergeSEOConfig(siteConfig, {
title: "About Us",
description: "Learn about our mission and team.",
canonical: buildCanonicalUrl(SITE_URL, "/about"),
openGraph: {
title: "About Us — My Site",
description: "Learn about our mission and team.",
url: buildCanonicalUrl(SITE_URL, "/about"),
},
});
// The Document renders <html>, <head>, <SEOHead>, and <body>
// You only write your page content here
return (
<Document seo={seo}>
<h1>About Us</h1>
<p>Our story...</p>
</Document>
);
}How it works together:
The Document receives your SEO config and renders the full HTML document. Here's what the final output looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<!-- Generated by SEOHead via Document -->
<title>About Us | My Site</title>
<meta name="description" content="Learn about our mission and team." />
<link rel="canonical" href="https://mysite.com/about" />
<meta property="og:title" content="About Us — My Site" />
<meta property="og:description" content="Learn about our mission and team." />
<meta property="og:url" content="https://mysite.com/about" />
<meta property="og:site_name" content="My Site" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@mysite" />
</head>
<body>
<nav><!-- shared navigation --></nav>
<main>
<h1>About Us</h1>
<p>Our story...</p>
</main>
<footer><!-- shared footer --></footer>
</body>
</html>5Add JSON-LD structured data
Structured data helps Google show rich results (star ratings, FAQs, breadcrumbs, etc). Pass schemas to the Document — it renders them inside <head> for you:
import {
mergeSEOConfig,
buildCanonicalUrl,
createArticleSchema,
createBreadcrumbSchema,
} from "react-ssr-seo-toolkit";
import { siteConfig, SITE_URL } from "../config/seo";
import { Document } from "../components/Document";
export function BlogPost() {
const seo = mergeSEOConfig(siteConfig, {
title: "My Blog Post",
description: "A short summary of the post.",
canonical: buildCanonicalUrl(SITE_URL, "/blog/my-post"),
});
// Create structured data schemas
const articleSchema = createArticleSchema({
headline: "My Blog Post",
url: buildCanonicalUrl(SITE_URL, "/blog/my-post"),
datePublished: "2025-06-15",
dateModified: "2025-07-01",
author: [
{ name: "Jane Doe", url: "https://mysite.com/authors/jane" },
],
publisher: {
name: "My Site",
logo: "https://mysite.com/logo.png",
},
images: ["https://mysite.com/images/post.jpg"],
});
const breadcrumb = createBreadcrumbSchema([
{ name: "Home", url: "https://mysite.com" },
{ name: "Blog", url: "https://mysite.com/blog" },
{ name: "My Blog Post", url: "https://mysite.com/blog/my-post" },
]);
// Pass schemas to Document — it renders them in <head>
return (
<Document seo={seo} schemas={[articleSchema, breadcrumb]}>
<article>
<h1>My Blog Post</h1>
<p>Post content here...</p>
</article>
</Document>
);
}Notice how the page never touches <html>, <head>, or <SEOHead> directly. It just defines what SEO data it needs — the Document handles where it goes.
6Framework integration
Every framework has a "layout" concept. The toolkit works with all of them. Choose your framework for a detailed guide:
React + Express SSR Guide
Complete guide with project structure, Document component, multi-page examples, server setup, and rendered HTML output.
Next.js Integration Guide
App Router with generateMetadata, Pages Router with <Head>, dynamic routes, JSON-LD, and reusable metadata helpers.
Here's a quick overview of how the pattern maps to each framework:
| Framework | Where <SEOHead> goes | Where JSON-LD goes | Guide |
|---|---|---|---|
| Express SSR | Document component (<head>) | Document component (<head>) | React Guide |
| Next.js App Router | Not used — use generateMetadata | Page body via safeJsonLdSerialize | Next.js Guide |
| Next.js Pages Router | Inside <Head> from next/head | Inside <Head> or page body | Next.js Guide |
| React Router 7 | root.tsx layout (<head>) | root.tsx layout (<head>) | React Guide |
| Remix | root.tsx layout (<head>) | root.tsx layout (<head>) | React Guide |
7Explore more
Continue learning with detailed guides and examples:
Multi-Page Usage
See a full app with home, listings, details, blog, and dashboard pages.
Recipes
Copy-paste ready code for 10+ common page types.
Common Mistakes
Avoid the most frequent SEO setup errors with clear examples.
FAQ
Answers to common questions about frameworks, SSR, and components.
API Reference
Complete docs for every function, component, and type.
Live Demos
Article, Product, FAQ, and No-Index pages with real SEO tags.
This very page has SEO tags! Right-click and select "View Page Source" to inspect the meta tags generated by react-ssr-seo-toolkit.