@dbosoft/nextjs-articles
v0.2.3
Published
Markdoc article rendering components and layout for dbosoft sites
Keywords
Readme
@dbosoft/nextjs-articles
Markdoc article rendering framework for dbosoft sites. Provides article page layouts, table of contents, prose styling, and a library of Markdoc tag components (callouts, steps, icons, etc.).
Installation
pnpm add @dbosoft/nextjs-articlesThis is a workspace package — add "@dbosoft/nextjs-articles": "workspace:*" to your package.json.
Peer Dependencies
react >=19, react-dom >=19, next >=16,
@heroicons/react >=2, @markdoc/markdoc >=0.4, clsx >=2Dependencies
@dbosoft/nextjs-site-core(workspace) — forArticlePageSEO,ResponsiveTable@dbosoft/nextjs-site-marketing(workspace) — forCodeFence,TerminalFrame
Subpath Exports
| Import | Description |
|--------|-------------|
| ./components | Article rendering: ArticleInfo, ArticleProse, ArticleImage, ArticleTableOfContents, ArticleTableOfContentsWrapper |
| ./markdoc | Markdoc tag components: Callout, Feature, SectionHeader, InlineIcon, Steps, Spacer, Divider |
| ./layout | Page layouts: ArticlePage, CategorySlugPage, CategoryPage + helpers |
Setup
1. Tailwind Content Paths
Content paths for @dbosoft packages are handled automatically by resolveDbosoftContent() in your Tailwind config (see @dbosoft/nextjs-site-core setup).
2. Article Data Loader
The package does not load articles from the filesystem — that stays in your app. You provide a loader function:
// lib/articles-server.ts
export async function getArticleBySlug(slugArray: string[]): Promise<Article | null> {
// Read markdown from filesystem, parse frontmatter, return article
}The loader must return objects matching the ArticleData interface:
interface ArticleData {
title: string
subtitle?: string
excerpt?: string
author?: string
publishDate: string
readTime?: string
category: string
content: string // raw markdown/markdoc content
}3. Article Page Configuration
Create a shared config file with site-specific settings. Most Markdoc tags/nodes are built-in — you only need siteUrl and defaultAuthor:
// app/(default)/components/article-config.ts
import { siteConfig } from '@/lib/seo'
import type { ArticlePageConfig } from '@dbosoft/nextjs-articles/layout'
export const articleConfig: ArticlePageConfig = {
siteUrl: siteConfig.url,
defaultAuthor: 'dbosoft',
}All standard tags (callout, steps, feature, terminal, etc.) and nodes (fence → CodeFence, table → ResponsiveTable) are pre-registered. You only need to add site-specific custom tags via config.components, config.tags, and config.nodes (see Customization).
4. Page Routes
Create thin wrapper components in your app:
// app/(default)/components/ArticlePage.tsx
import { ArticlePage as ArticlePageBase } from '@dbosoft/nextjs-articles/layout'
import { getArticleBySlug } from '@/lib/articles-server'
import { articleConfig } from './article-config'
export default async function ArticlePage({ category, slug, article }) {
return (
<ArticlePageBase
category={category}
slug={slug}
article={article}
articleLoader={getArticleBySlug}
config={articleConfig}
/>
)
}// app/(default)/guides/[slug]/page.tsx
import CategorySlugPage from '../../components/CategorySlugPage'
export default async function GuidePage({ params }) {
return (
<CategorySlugPage
category="guides"
categoryTitle="Guides"
params={await params}
/>
)
}// app/(default)/guides/page.tsx
import CategoryPage from '../components/CategoryPage'
export default function GuidesPage() {
return (
<CategoryPage
category="guides"
title="Guides"
description="Step-by-step guides for setting up and using MaxBack."
emptyMessage="No guides available yet."
/>
)
}Built-in Markdoc Tags
These tags are pre-registered in ArticlePage and available in all article markdown:
{% callout %} — Info/Warning/Success boxes
{% callout type="info" title="Note" %}
Important information here.
{% /callout %}
{% callout type="warning" title="Caution" %}
Be careful with this step.
{% /callout %}Types: info, warning, success, tip, feature
{% sectionheader %} — Styled H2 with gradient accent
{% sectionheader id="installation" %}Installation{% /sectionheader %}Auto-generates IDs for the table of contents.
{% feature %} — Feature list items
{% feature title="Automatic Backups" %}
Backups run on schedule with zero manual intervention.
{% /feature %}{% steps %} — Numbered step-by-step instructions
{% steps title="Getting Started" %}
1. **Download** the installer from the downloads page.
2. **Install** using the setup wizard.
3. **Configure** the VSS Writer service.
{% /steps %}{% image %} — Images with captions and zoom modal
{% image src="/images/dashboard.png" alt="Dashboard" caption="The MaxBack monitoring dashboard" width="800" height="600" /%}Attributes: src, alt, width, height, caption, priority, sizes, maxWidth
{% icon %} — Inline icons
{% icon type="check" /%} Supported
{% icon type="cross" /%} Not supportedTypes: check, cross, warning, info, arrow, lightning, sparkle
{% spacer %} / {% divider %}
{% spacer size="lg" /%}
{% divider style="dots" /%}Spacer sizes: sm, md, lg. Divider styles: line, dots, fade.
{% terminal %} — Terminal/shell code block
{% terminal title="Install MaxBack" %}
```bash
msiexec /i maxback-setup.msi
```
{% /terminal %}Code fences and tables
Code fences (```) automatically render with CodeFence (syntax-highlighted via Prism). Tables render with ResponsiveTable (mobile-friendly with data-label attributes). No custom tags needed — these are handled as built-in Markdoc node overrides.
{% article-info %} — Article metadata banner
{% article-info author="John Doe" publishDate="2025-01-15" readTime="5 min read" %}
A brief summary of this article.
{% /article-info %}Customization
Adding Site-Specific Tags
Register custom tags via ArticlePageConfig.tags and their React components via ArticlePageConfig.components:
// article-config.ts
import PricingLink from '@/components/markdoc/PricingLink'
export const articleConfig: ArticlePageConfig = {
components: {
PricingLink,
// ...other components
},
tags: {
'pricing-link': {
render: 'PricingLink',
attributes: {
plan: { type: String, required: true },
},
selfClosing: true,
},
},
}Then use in markdown:
{% pricing-link plan="subscription" /%}Site-specific tags/components are merged with built-ins. If a name collides, the site-specific version wins.
Overriding Built-in Components
To replace a built-in component (e.g., custom Callout styling), add it to config.components with the same key:
import CustomCallout from '@/components/CustomCallout'
export const articleConfig: ArticlePageConfig = {
components: {
Callout: CustomCallout, // overrides built-in Callout
},
}CategoryPage with Custom Article Loaders
CategoryPage accepts an articlesLoader callback. This lets each site control how articles are fetched:
import { CategoryPage } from '@dbosoft/nextjs-articles/layout'
import type { ArticleListItem } from '@dbosoft/nextjs-articles/layout'
async function loadArticles(category: string): Promise<ArticleListItem[]> {
// Fetch from CMS, filesystem, database, etc.
return [
{ title: 'Article 1', slug: 'guides/article-1', publishDate: '2025-01-01' },
]
}
<CategoryPage
category="guides"
title="Guides"
description="All guides"
emptyMessage="No guides yet."
articlesLoader={loadArticles}
defaultAuthor="Your Team"
/>generateCategoryMetadata
Helper for Next.js generateMetadata in category slug pages:
import { generateCategoryMetadata } from '@dbosoft/nextjs-articles/layout'
import { getArticleBySlug } from '@/lib/articles-server'
export async function generateMetadata({ params }) {
const { slug } = await params
return generateCategoryMetadata(
'guides',
'Guides',
slug,
getArticleBySlug,
{ siteName: 'MaxBack', defaultAuthor: 'dbosoft' }
)
}Prose Styling
ArticleProse applies Tailwind Typography (prose dark:prose-invert) with opinionated defaults for headings, code blocks, blockquotes, and tables. To override, wrap it and pass a className:
import { ArticleProse } from '@dbosoft/nextjs-articles/components'
<ArticleProse className="prose-headings:text-blue-900">
{content}
</ArticleProse>Table of Contents
ArticleTableOfContentsWrapper auto-extracts headings from the DOM (article h2[id], h3[id], h4[id]). It renders as a sticky sidebar on desktop and a collapsible dropdown on mobile. No configuration needed — just ensure your headings have id attributes (which SectionHeader does automatically).
