@earlyseo/blog
v1.0.2
Published
Drop-in blog integration for React & Next.js — powered by EarlySEO. Articles are served from EarlySEO's CDN. Just add your site ID and render.
Maintainers
Readme
@earlyseo/blog
Drop-in blog integration for React & Next.js — powered by EarlySEO.
Articles are served from the EarlySEO CDN. No database, no webhooks, no storage config — just add your site ID and go.
Features
- CDN-first — Articles served from EarlySEO's global CDN. Zero infrastructure to manage.
- Paginated — Pre-built paginated JSON. No DB queries per visitor.
- Next.js App Router — Drop-in server components for
/blogand/blog/[slug] - React hooks —
useArticles()anduseArticle(slug)for custom UIs - Full SEO — Open Graph, Twitter cards, meta descriptions, canonical URLs
- Auto-styling — CSS custom properties adapt to your site's theme
- 100% customizable — Render props, custom components, or headless hooks
Quick Start (Next.js)
Option A: Auto-generate pages (recommended)
npm install @earlyseo/blog
npx earlyseo-blogThe CLI will detect your Next.js project, ask for your site ID, and generate:
app/blog/page.tsx— Paginated blog listingapp/blog/[slug]/page.tsx— Article detail with full SEO metadata.env.local—EARLYSEO_SITE_IDadded automatically
That's it — run npm run dev and visit /blog.
Option B: Manual setup
1. Install
npm install @earlyseo/blog2. Set your site ID
# .env.local
EARLYSEO_SITE_ID=your-site-id3. Add the blog listing page
// app/blog/page.tsx
import { createBlogListPage, createBlogListMetadata } from "@earlyseo/blog/next";
export default createBlogListPage({
siteId: process.env.EARLYSEO_SITE_ID!,
title: "Our Blog",
});
export const generateMetadata = createBlogListMetadata({
title: "Our Blog",
description: "Read our latest articles about...",
siteName: "My Site",
});4. Add the article detail page
// app/blog/[slug]/page.tsx
import { createBlogPostPage, createBlogPostMetadata } from "@earlyseo/blog/next";
const siteId = process.env.EARLYSEO_SITE_ID!;
export default createBlogPostPage({ siteId });
export const generateMetadata = createBlogPostMetadata({ siteId, siteName: "My Site" });That's it! Articles published from EarlySEO automatically appear on your blog via CDN.
How It Works
When you publish an article in EarlySEO, we write pre-built JSON files to our CDN:
media.earlyseo.com/site/{siteId}/manifest.json → Total pages, version
media.earlyseo.com/site/{siteId}/articles/page-1.json → Paginated article list
media.earlyseo.com/site/{siteId}/article/my-slug.json → Full article contentYour site reads from these JSON files — no database, no API calls, no webhook handlers.
Only the affected files are updated on each publish:
- The individual article JSON
- The paginated list pages
- The manifest
React Components
Using with any React framework (Vite, Remix, etc.)
Wrap your app in EarlySeoProvider and use the components:
import { EarlySeoProvider, BlogList, BlogPost, ArticleStyles } from "@earlyseo/blog/react";
function App() {
return (
<EarlySeoProvider siteId="your-site-id" basePath="/blog">
<ArticleStyles />
<Routes>
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:slug" element={<BlogPostRoute />} />
</Routes>
</EarlySeoProvider>
);
}
function BlogPostRoute() {
const { slug } = useParams();
return <BlogPost slug={slug!} />;
}Headless Hooks
For full control over the UI, use the hooks directly:
import { useArticles, useArticle } from "@earlyseo/blog/react";
function CustomBlogList() {
const { articles, loading, page, totalPages, goToPage } = useArticles();
if (loading) return <Skeleton />;
return (
<div>
{articles.map((a) => (
<Link key={a.slug} to={`/blog/${a.slug}`}>
<h2>{a.title}</h2>
<p>{a.metaDescription}</p>
</Link>
))}
<div>Page {page} of {totalPages}</div>
</div>
);
}
function CustomArticle({ slug }: { slug: string }) {
const { article, loading } = useArticle(slug);
if (loading) return <Skeleton />;
if (!article) return <NotFound />;
return (
<article>
<h1>{article.title}</h1>
<div dangerouslySetInnerHTML={{ __html: article.contentRawHtml }} />
</article>
);
}Custom Card Rendering
Override the default card in the blog list:
<BlogList
renderCard={(article, basePath) => (
<a href={`${basePath}/${article.slug}`} className="my-custom-card">
<img src={article.imageUrl} alt={article.title} />
<h3>{article.title}</h3>
</a>
)}
/>CDN Client
Use EarlySeoClient directly for full control:
import { EarlySeoClient } from "@earlyseo/blog";
const client = new EarlySeoClient(
{ siteId: "your-site-id" },
// Optional: Next.js ISR caching
{ next: { revalidate: 60 } }
);
const manifest = await client.getManifest(); // Manifest | null
const page1 = await client.getListPage(1); // ArticleListPage | null
const article = await client.getArticle("slug"); // Article | nullStyling & Theming
Articles come with a .earlyseo-article CSS wrapper that uses CSS custom properties for full theming control:
:root {
--earlyseo-font-family: "Inter", sans-serif;
--earlyseo-text-color: #1a1a1a;
--earlyseo-heading-color: #111;
--earlyseo-link-color: #2563eb;
--earlyseo-border-color: #e5e7eb;
--earlyseo-code-bg: #f3f4f6;
--earlyseo-pre-bg: #f9fafb;
--earlyseo-th-bg: #f3f4f6;
--earlyseo-muted-color: #6b7280;
--earlyseo-tag-bg: #f3f4f6;
--earlyseo-blog-max-width: 72rem;
--earlyseo-post-max-width: 48rem;
--earlyseo-blog-padding: 2rem 1rem;
}Or use htmlMode="raw" to strip all EarlySEO styles and use your own:
// Next.js
export default createBlogPostPage({ siteId, htmlMode: "raw" });
// React
<BlogPost slug={slug} htmlMode="raw" />API Reference
Types
interface Manifest {
schemaVersion: 1;
version: number;
totalArticles: number;
totalPages: number;
pageSize: number;
updatedAt: string;
featuredSlugs: string[];
}
interface ArticleListItem {
title: string;
slug: string;
imageUrl?: string;
imageAlt?: string;
metaDescription: string;
createdAt: string;
tags: string[];
}
interface ArticleListPage {
page: number;
totalPages: number;
totalArticles: number;
version: number;
articles: ArticleListItem[];
}
interface Article {
id: string;
title: string;
slug: string;
contentHtml: string; // Styled HTML (with .earlyseo-article wrapper)
contentRawHtml: string; // Raw HTML (inherits your site theme)
contentCss: string; // Standalone CSS for .earlyseo-article
metaDescription: string;
createdAt: string;
updatedAt: string;
imageUrl?: string;
imageAlt?: string;
tags: string[];
summary?: string;
canonicalUrl?: string;
version: number;
}Imports
| Import Path | Contents |
|-------------|----------|
| @earlyseo/blog | EarlySeoClient, core types, CSS constants |
| @earlyseo/blog/react | Provider, hooks, components |
| @earlyseo/blog/next | Next.js page creators, metadata helpers |
| @earlyseo/blog/css | ARTICLE_CSS, BLOG_CSS |
License
Proprietary — see LICENSE for details.
