@gosalesdeal/blog
v0.2.0
Published
Render GoEnterprise blog content (list + post) straight from the BlogPosts API. Server-component friendly, zero UI dependencies.
Downloads
390
Readme
@gosalesdeal/blog
Render the GoEnterprise blog (list + post detail) in any React 18+ / Next.js App Router app, straight from the BlogPosts API. Server-component friendly, with a typed data client and zero UI dependencies.
Install
npm install @gosalesdeal/blogreact (and react-dom) are peer dependencies.
Configure
Components read config from props, then configureBlog(...) defaults, then env vars (BLOG_API_URL / NEXT_PUBLIC_BLOG_API_URL, BLOG_TENANT_ID / NEXT_PUBLIC_BLOG_TENANT_ID).
// e.g. instrumentation.ts or a server entry
import { configureBlog } from '@gosalesdeal/blog';
configureBlog({
apiBaseUrl: process.env.BLOG_API_URL!, // base of the BlogPosts API
tenantId: process.env.BLOG_TENANT_ID!, // sent as the X-Tenant header
});Next.js App Router usage
// app/blog/page.tsx
import { BlogList, generateBlogListMetadata } from '@gosalesdeal/blog';
import '@gosalesdeal/blog/styles.css';
export const metadata = generateBlogListMetadata({ siteName: 'Acme' });
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
const { page } = await searchParams;
return <BlogList page={Number(page) || 1} basePath="/blog" />;
}// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { BlogPost, getPostBySlug, getPublishedSlugs, generateBlogPostMetadata } from '@gosalesdeal/blog';
import '@gosalesdeal/blog/styles.css';
export async function generateStaticParams() {
const slugs = await getPublishedSlugs();
return slugs.map((slug) => ({ slug }));
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return {};
return generateBlogPostMetadata(post, { siteUrl: 'https://acme.com', siteName: 'Acme' });
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return (
<BlogPost
slug={slug}
seo={{ siteUrl: 'https://acme.com', siteName: 'Acme' }}
fallback={notFound()}
/>
);
}API
Components
<BlogList page limit category basePath apiBaseUrl tenantId locale emptyState />— paginated listing of cards.<BlogPost slug seo trackView renderJsonLd locale fallback />— full post with header, block body, and JSON-LD.<BlockRenderer block />/renderBlocks(blocks)— render rawContentBlocks yourself.
Data client
createBlogClient({ apiBaseUrl, tenantId }) → { getPostBySlug, getPosts, getPostsByCategory, getPublishedSlugs, getCategories, incrementView }. The same functions are exported standalone (each accepts an optional trailing config).
SEO
generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostJsonLd, generateBlogBreadcrumbJsonLd — framework-neutral plain objects (assignable to Next's Metadata).
Styling
The bundled @gosalesdeal/blog/styles.css is optional and namespaced under .ggblog-*. Theme it by overriding the CSS variables on .ggblog-post / .ggblog-list (--ggblog-accent, --ggblog-fg, --ggblog-border, …), or skip it and bring your own.
Scope & notes
- Renders block-based posts (
content_blocks). All 17 block types are supported;accordionandtabsare interactive ("use client"), everything else is a server component. - Block HTML (text/embed/accordion/tab content) is treated as trusted CMS output. If your authors are untrusted, sanitize upstream.
- The page-builder (
landingPage.schema_json) rendering path is out of scope.
License
MIT
