@fullstackdatasolutions/articles 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +226 -29
  3. package/dist/index.cjs +635 -274
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +165 -56
  6. package/dist/index.d.ts +165 -56
  7. package/dist/index.js +614 -250
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +40 -5
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +72 -0
  12. package/dist/nextjs.d.ts +72 -0
  13. package/dist/nextjs.js +40 -5
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +280 -16
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +92 -3
  18. package/dist/server.d.ts +92 -3
  19. package/dist/server.js +269 -16
  20. package/dist/server.js.map +1 -1
  21. package/package.json +8 -5
  22. package/src/ArticleDetailHero.tsx +27 -2
  23. package/src/ArticleSchemas.tsx +27 -27
  24. package/src/AuthorArticlesPage.tsx +60 -0
  25. package/src/AuthorCard.tsx +112 -0
  26. package/src/AuthorDetailHero.tsx +56 -0
  27. package/src/Breadcrumb.tsx +78 -0
  28. package/src/CategoryArticlesPage.tsx +62 -11
  29. package/src/__tests__/ArticleDetailHero.test.tsx +21 -1
  30. package/src/__tests__/ArticleSchemas.test.tsx +47 -2
  31. package/src/__tests__/AuthorArticlesPage.test.tsx +74 -0
  32. package/src/__tests__/AuthorCard.test.tsx +98 -0
  33. package/src/__tests__/AuthorDetailHero.test.tsx +51 -0
  34. package/src/__tests__/CategoryArticlesPage.test.tsx +31 -5
  35. package/src/__tests__/articlesConfig.test.ts +20 -1
  36. package/src/__tests__/authorUtils.test.ts +89 -0
  37. package/src/__tests__/renderMdx.test.tsx +113 -0
  38. package/src/__tests__/seoUtils-authors.test.ts +160 -0
  39. package/src/__tests__/seoUtils.test.ts +4 -0
  40. package/src/__tests__/server-articles.test.ts +159 -2
  41. package/src/articleTypes.ts +33 -0
  42. package/src/articlesConfig.ts +68 -0
  43. package/src/authorUtils.ts +95 -0
  44. package/src/index.ts +32 -9
  45. package/src/renderMdx.tsx +8 -2
  46. package/src/seoUtils.ts +226 -7
  47. package/src/server-articles.ts +98 -10
  48. package/src/server.ts +19 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,243 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.11.0] - 2026-07-30
9
+
10
+ ### Added
11
+
12
+ - Added `mdxComponents` to `ArticlesConfig`, allowing consuming apps to register React components for explicit use inside article MDX bodies. Registered components are merged with the package's built-in MDX image override.
13
+
14
+ ## [0.10.0] - 2026-07-23
15
+
16
+ ### Added
17
+
18
+ - Added configured author profiles with social links, avatar support, default authors, and optional generated author pages.
19
+ - Added zero-author and multi-author article support via `authors: []` and `authors: [author-slug]` frontmatter while preserving the legacy `author` fallback.
20
+ - Added author social profile fields for website, Facebook, Twitter, X, LinkedIn, Instagram, YouTube, TikTok, GitHub, Bluesky, Threads, Mastodon, Medium, newsletter, and custom `other` links. Empty social fields are omitted from rendered author UI.
21
+ - Added author server utilities: `getAuthorBySlug`, `getArticleAuthors`, `getAllAuthors`, `getArticlesByAuthor`, `generateAuthorStaticParams`, and `generateAuthorMetadata`.
22
+ - Added `AuthorCard`, `AuthorDetailHero`, `AuthorArticlesPage`, and visible `Breadcrumb` components.
23
+ - Added optional breadcrumb builders for article, category, and author pages with configurable trails, custom URL entries at any position, folder-path expansion, label overrides, separators, and JSON-LD control.
24
+
25
+ ### Changed
26
+
27
+ - Article JSON-LD now emits full `Person` objects for configured authors.
28
+ - Article, category, search, feed, and sitemap helpers can resolve configured author display names when passed `ArticlesConfig`.
29
+ - The reference app now exposes `/articles/authors/[author]` pages, shows configured author bylines, and uses visible breadcrumbs on article and category routes.
30
+
31
+ ## [0.9.0] - 2026-07-06
32
+
33
+ ### Added
34
+
35
+ - Added `linkTargetStrategy` to `ArticlesConfig` with three article body link behaviors: `external-new-tab` (default), `all-new-tab`, and `same-tab`.
36
+ - Added `generateRssFeed(articles, config)` to the server exports so consuming apps can delegate RSS XML generation to the package.
37
+
38
+ ### Changed
39
+
40
+ - Article body links now default to opening internal links in the same window and external `http`/`https` links in a new window with `rel="noopener noreferrer"`.
41
+ - `getArticleMetadata(slug, config?)`, `ArticleContent`, and MDX rendering now accept article config so Markdown and MDX links share the same target behavior.
42
+ - The README article route examples now pass `siteConfig` into article metadata/content rendering, and the RSS route example uses `generateRssFeed`.
43
+
44
+ ## [0.8.2] - 2026-05-27
45
+
46
+ ### Added
47
+
48
+ - Added test coverage for `aiCrawl` wiring: robots.txt AI rules integration, markdown route handler (`GET /articles/[slug].md`), and `generateMetadata` aiCrawl behavior in the article detail page.
49
+
50
+ ### Documentation
51
+
52
+ - README: added concrete wiring examples for `aiCrawl` integration in consuming apps - `getAiRobotsTxtRules` usage in `robots.txt` route, `generateMetadata` alternates pattern for markdown alternate links, and `robots` string for `noai`/`noimageai` directives.
53
+ - README: added explicit warning that `headers()` from `next/headers` is read-only in Next.js App Router pages and cannot be used to set response headers - `getArticleAiHeaders` must only be called from Route Handlers.
54
+
55
+ ### Fixed
56
+
57
+ - Removed dead code in article detail page that referenced `headers()` and `getArticleAiHeaders` in a Server Component context, causing TypeScript errors.
58
+
59
+ ## [0.8.1] - 2026-06-01
60
+
61
+ ## [0.8.0] - 2026-05-26
62
+
63
+ ### Added
64
+
65
+ - Added `aiCrawl` article frontmatter support. Only articles with `aiCrawl: true` expose markdown twins, markdown alternate metadata, and markdown route helper responses.
66
+ - Added AI crawl server helpers: `getArticleMarkdown`, `getArticleMarkdownResponse`, `getArticleMarkdownUrl`, `getArticleAiHeaders`, and `getAiRobotsTxtRules`.
67
+ - Added `showAuthor` to `ArticlesConfig` to hide author names from UI, generated metadata, JSON-LD, RSS examples, and optional author search matching.
68
+ - Added `setArticlesErrorHandler` so installed apps can route article package errors to their own logging or observability system.
69
+
70
+ ### Dependencies
71
+
72
+ - `remark-github-blockquote-alert` updated from `^1.3.1` to `^2.1.0`
73
+
74
+ ## [0.7.2] - 2026-05-22
75
+
76
+ ### Added
77
+
78
+ - Added npm `funding` metadata pointing to the Buy Me a Coffee support page.
79
+ - Added a dependency-free `postinstall` message that thanks users for installing the package and links to the support page.
80
+
81
+ ## [0.7.1] - 2026-05-21
82
+
83
+ ### Fixed
84
+
85
+ - Internal anchor links (href starting with `#`) no longer open in a new tab. The `customRenderer` rehype plugin now checks for internal anchors before applying `target="_blank"` and `rel="noopener noreferrer"`.
86
+ - Article discovery now continues walking inside directories that are also articles, so nested articles at any depth are included in `getAllArticles()`, sitemap entries, RSS feeds, and static params.
87
+ - README route examples now use the catch-all `app/articles/[...slug]/page.tsx` pattern required for slash-separated nested article slugs.
88
+
89
+ ### Added
90
+
91
+ - `ArticleBackLink` component - renders a back-navigation link on article detail pages. Returns `null` when `config.showBackToArticles === false`. Accepts `config`, optional `href` (default `/articles`), optional `label` (default `Back to Articles`), and optional `className` props.
92
+ - Test coverage added across `markdown.ts`, `ArticleBackLink.tsx`, `articlesConfig.ts`, and `server-articles.ts`, including coverage for nested article discovery.
93
+
94
+ ## [0.7.0] - 2026-05-20
95
+
96
+ ### Added
97
+
98
+ - MDX articles now apply the same explicit Tailwind utility classes as Markdown articles. The shared `customRenderer` rehype plugin is passed to `evaluate()` so heading, paragraph, list, blockquote, code, table, and image elements are styled identically across both content types.
99
+ - MDX image resolution: relative `src` values in MDX articles are resolved to `/articles/{slug}/{filename}` at render time via an injected `img` component. Absolute URLs and paths starting with `/` are left unchanged.
100
+ - MDX rendering now includes `remarkGfm`, `remarkGithubBlockquoteAlert`, `rehypeSlug`, `rehypeAutolinkHeadings`, and `rehypePrism` - matching the full Markdown pipeline.
101
+
102
+ ### Changed
103
+
104
+ - `customRenderer` in `markdown.ts` is now exported so it can be shared between the Markdown and MDX pipelines.
105
+ - `ArticleContent` passes `/articles/{article.slug}` as `basePath` to `renderMdxSource`, enabling correct image URL resolution for both flat and nested article slugs.
106
+ - `renderMdxSource` accepts an optional `basePath` parameter. When provided, a custom `img` component is injected into the MDX component tree to resolve relative image paths.
107
+
108
+ ## [0.6.0] - 2026-05-21
109
+
110
+ ### Changed
111
+
112
+ - `getAvailableArticleSlugs()` now discovers article directories recursively under `public/articles`, so nested articles such as `public/articles/game-system/article-name/article.mdx` produce the slug `game-system/article-name`.
113
+
114
+ ## [0.5.1] - 2026-05-20
115
+
116
+ ### Added
117
+
118
+ - `ArticleTOC` is now exported from `@fullstackdatasolutions/articles/server`, so server route examples can import the server component without pulling from the client entry point.
119
+
120
+ ### Changed
121
+
122
+ - README route examples now use the actual article detail wiring with `getArticleMetadata`, `ArticleContent`, `ArticleDetailHero`, `ArticleSEO`, social sharing, comments, navigation, and scroll-to-top instead of referencing a nonexistent `ArticlePage` export.
123
+ - README category route examples now import `CategoryArticlesPage` from the main package entry and fetch category articles with `getArticlesByCategory`.
124
+ - README frontmatter reference now documents `lastmod`, `draft`, `faq`, `howTo`, `canonicalUrl`, `articleType`, `series`, MDX support, image resolution, generated reading time, and generated table of contents.
125
+ - README server utility examples now include all exported helpers, including static param generators, markdown helpers, `sanitizeImagePath`, `ArticleContent`, and `ArticleTOC`.
126
+
127
+ ### Fixed
128
+
129
+ - README comments setup now includes the required `app/api/articles/comments-store.ts` helper and correctly documents file-backed `comments.json` storage instead of PostgreSQL/Prisma storage.
130
+ - README `ArticleTOC` guidance now imports from `@fullstackdatasolutions/articles/server`, matching the server-only usage guidance.
131
+
132
+ ## [0.5.0] - 2026-05-19
133
+
134
+ ### Added
135
+
136
+ - `ArticleTOC` component - server component (no `'use client'`) that renders `article.toc[]` as an inline nav block above article content. Indentation: h2=0rem, h3=1rem, h4=2rem (based on `item.depth - 2`). Returns `null` when toc is empty. Controlled by `siteConfig.showToc` (gate: `showToc !== false && article.toc?.length > 0`). Exported from index.ts.
137
+ - `ScrollToTop` component - client component (`'use client'`) that renders a fixed-position floating button (bottom-right corner) appearing when user scrolls past 300px. Smooth-scrolls back to top on click. No props. Exported from index.ts.
138
+ - `showToc?: boolean` field on `ArticlesConfig` - optional, defaults to `true` when omitted. Set to `false` to hide the table of contents on all article detail pages.
139
+ - `description?: string` field on `ArticlesConfig` - optional. Short description used as the RSS feed channel description. Falls back to `siteName` if omitted.
140
+ - RSS feed route reference implementation - copy `app/articles/feed.xml/route.ts` from the reference app into consuming apps to enable an RSS 2.0 feed at `/articles/feed.xml`. Uses `force-static` for build-time generation. Uses `getAllArticles` from `@fullstackdatasolutions/articles/server`. Uses `siteConfig.description` for channel description field.
141
+
142
+ ### Changed
143
+
144
+ - `generateArticlesIndexMetadata` now includes `alternates.types['application/rss+xml']` pointing to `${siteUrl}/articles/feed.xml`, automatically adding `<link rel="alternate" type="application/rss+xml">` to the articles index page head.
145
+
146
+ ## [0.4.3] - 2026-05-18
147
+
148
+ ### Added
149
+
150
+ - `generateArticlesIndexMetadata(config: ArticlesConfig): Metadata` - generates full Next.js metadata for the articles index page (`/articles`). Returns title, description, OpenGraph (`type: 'website'`), Twitter Card, canonical URL, and robots directives. Description falls back to `config.hero?.description` when set. Export from `@fullstackdatasolutions/articles/server`.
151
+
152
+ ## [0.4.2] - 2026-05-18
153
+
154
+ ### Changed
155
+
156
+ - `getArticleSitemapEntries` now accepts `string | ArticlesConfig`. Pass your `ArticlesConfig` directly instead of extracting `config.siteUrl` manually. Passing a plain URL string still works (backwards compatible).
157
+
158
+ ## [0.4.1] - 2026-05-18
159
+
160
+ ### Fixed
161
+
162
+ - `CategoryArticlesPage` hero section now uses inline styles for critical layout (`position`, `height`, `overflow`, `z-index`, overlay background). Previously relied on Tailwind classes (`relative h-72 md:h-96`, `absolute inset-0`, `bg-black/60`, `relative z-10`) which have no effect on consuming apps that do not configure Tailwind to scan the package source. Follows the same fix applied to `ArticleCategoryGrid` images in v0.2.1.
163
+ - `ArticleDetailHero` now defaults `categoryBasePath` to `'/articles/category'`, so category tags render as links out of the box. Pass `categoryBasePath=""` to suppress links. Previously omitting the prop produced plain `<span>` elements with no navigation.
164
+ - `ArticleDetailHero` `<Image>` now includes `style={{ objectFit: 'cover' }}` in addition to `className="object-cover"`, ensuring the image fills the hero container correctly on consuming apps where Tailwind may not process the package source.
165
+
166
+ ## [0.4.0] - 2026-05-18
167
+
168
+ ### Added
169
+
170
+ - `ArticleSocialShare` component: social sharing buttons for LinkedIn, Facebook, Twitter, Reddit, WhatsApp, Telegram, Email, and Copy Link. Optional `shareMessage` prop renders a footer paragraph below the buttons. Exported from the main package entry point.
171
+ - `ArticleNavigation` component: previous/next article navigation links. Accepts `previous`, `next` (each `{ slug, title } | null`), and `basePath` props. Returns `null` when both are absent. Titles longer than 60 characters are truncated. Exported from the main package entry point.
172
+
173
+ ### Changed
174
+
175
+ - `ArticleDetailHero` now accepts an optional `showDate?: boolean` prop (default `false`). The article date is hidden by default; pass `showDate={true}` to render it.
176
+
177
+ ## [0.3.0] - 2026-05-14
178
+
179
+ ### Added
180
+
181
+ - `ArticleDetailHero` component: displays the article's featured image behind the title, categories, and metadata in the article detail page hero. Exported from the main package entry point. Accepts `article` and optional `categoryBasePath` props.
182
+
183
+ ### Changed
184
+
185
+ - ArticleDetailHero now displays at most 4 category tags in the article hero. Articles with more than 4 categories show only the first 4.
186
+
187
+ ### Dependencies
188
+
189
+ - `lucide-react` upgraded from `^0.454.0` to `^1.16.0`
190
+ - `rehype-highlight` upgraded from `^7.0.1` to `^7.0.2`
191
+ - `rehype-prism-plus` upgraded from `^2.0.0` to `^2.0.2`
192
+ - `remark-gfm` upgraded from `^4.0.0` to `^4.0.1`
193
+ - `unist-util-visit` upgraded from `^5.0.0` to `^5.1.0`
194
+
195
+ ## [0.2.2] - 2026-05-14
196
+
197
+ ### Changed
198
+
199
+ - Category grid now uses the same responsive layout as Latest Articles: 1 column on mobile, 2 columns at md, 3 columns at lg. Removed conflicting inline `gridTemplateColumns` style that was overriding Tailwind responsive classes.
200
+
201
+ ## [0.2.1] - 2026-05-14
202
+
203
+ ### Added
204
+
205
+ - `keywords` field added to `package.json` for npm discoverability
206
+ - Blog link to [fullstackdatasolutions.com](https://fullstackdatasolutions.com) added to README
207
+
208
+ ### Fixed
209
+
210
+ - Category grid now renders single column on mobile (was 2 columns). Changed `grid-cols-2` to `grid-cols-1`; desktop 4-column layout is unchanged.
211
+ - Category images now render correctly on all consuming apps regardless of whether `@source` is configured in the app's Tailwind CSS. The image container's `position: relative` and `height` are now set via inline styles instead of Tailwind classes (`relative h-52`), which were only generated when the consuming app scanned the package source.
212
+
213
+ No migration needed - both fixes are internal and backwards-compatible.
214
+
215
+ ## [0.2.0] - 2026-05-13
216
+
217
+ ### Added
218
+
219
+ - `HeroConfig` interface with optional `title` and `description` fields
220
+ - `hero` field on `ArticlesConfig` (type `HeroConfig`, fully optional)
221
+ - `ArticlesHero` now accepts `title` and `description` props; built-in strings remain as defaults
222
+
223
+ ### Changed
224
+
225
+ - `renderSection` internal function signature extended with a `config` parameter to forward hero config
226
+ - `frontend/config/articles.ts` (reference app) now explicitly sets `hero.title` and `hero.description` matching the previous hardcoded values
227
+
228
+ ### Migration
229
+
230
+ No breaking changes. Existing `ArticlesConfig` objects without a `hero` field continue to render the original "Vox Populus Insights" heading and description unchanged.
231
+
232
+ To customize the hero for your app, add:
233
+
234
+ ```ts
235
+ hero: {
236
+ title: 'Your App Title',
237
+ description: 'Your custom description.',
238
+ }
239
+ ```
240
+
241
+ ## [0.1.0] - initial release
242
+
243
+ - Initial public release with articles listing, article detail, category pages, comments system, SEO utilities, and theming support.
package/README.md CHANGED
@@ -74,6 +74,33 @@ export const siteConfig: ArticlesConfig = {
74
74
  siteUrl: 'https://yoursite.com',
75
75
  siteName: 'Your Site',
76
76
  pageSize: 6,
77
+ defaultAuthor: 'jane-doe',
78
+ authors: {
79
+ 'jane-doe': {
80
+ slug: 'jane-doe',
81
+ name: 'Jane Doe',
82
+ bio: 'Jane writes about campaign operations and civic technology.',
83
+ avatar: '/authors/jane-doe.jpg',
84
+ social: {
85
+ website: 'https://yoursite.com/about',
86
+ facebook: 'janedoe',
87
+ twitter: '@janedoe',
88
+ x: 'janedoe',
89
+ linkedin: 'https://www.linkedin.com/in/janedoe',
90
+ other: {
91
+ Podcast: 'https://example.com/podcast',
92
+ },
93
+ },
94
+ },
95
+ },
96
+ breadcrumbs: {
97
+ article: [
98
+ { name: 'Resources', url: '/resources' },
99
+ 'primaryCategory',
100
+ 'folderPath',
101
+ 'articleTitle',
102
+ ],
103
+ },
77
104
  }
78
105
  ```
79
106
 
@@ -97,16 +124,18 @@ import {
97
124
  ArticleNavigation,
98
125
  ArticleSEO,
99
126
  ArticleSocialShare,
100
- BreadcrumbSchema,
127
+ Breadcrumb,
101
128
  CommentsSection,
102
129
  ScrollToTop,
103
130
  } from '@fullstackdatasolutions/articles'
104
131
  import {
105
132
  ArticleContent,
106
133
  ArticleTOC,
134
+ buildArticleBreadcrumbs,
107
135
  generateArticleMetadata,
108
136
  generateArticleStaticParams,
109
137
  getAdjacentArticles,
138
+ getArticleAuthors,
110
139
  getArticleMetadata,
111
140
  } from '@fullstackdatasolutions/articles/server'
112
141
  import { siteConfig } from '@/config/articles'
@@ -128,19 +157,24 @@ export default async function Page({ params }: ArticlePageProps) {
128
157
  const article = await getArticleMetadata(slug, siteConfig)
129
158
  if (!article) notFound()
130
159
  const { previous, next } = await getAdjacentArticles(slug)
160
+ const authors = getArticleAuthors(article, siteConfig)
161
+ const breadcrumbItems = buildArticleBreadcrumbs(article, siteConfig)
131
162
  const siteUrl = siteConfig.siteUrl.replace(/\/$/, '')
132
163
  const articleUrl = `${siteUrl}/articles/${slug}`
133
164
  return (
134
165
  <>
135
- <ArticleSEO article={article} articleUrl={articleUrl} siteName={siteConfig.siteName} />
136
- <BreadcrumbSchema
137
- items={[
138
- { name: 'Home', url: siteUrl },
139
- { name: 'Articles', url: `${siteUrl}/articles` },
140
- { name: article.title, url: articleUrl },
141
- ]}
166
+ <ArticleSEO
167
+ article={article}
168
+ articleUrl={articleUrl}
169
+ siteName={siteConfig.siteName}
170
+ authors={authors}
171
+ />
172
+ <Breadcrumb items={breadcrumbItems} />
173
+ <ArticleDetailHero
174
+ article={article}
175
+ authors={authors}
176
+ categoryBasePath="/articles/category"
142
177
  />
143
- <ArticleDetailHero article={article} categoryBasePath="/articles/category" />
144
178
  {siteConfig.showToc !== false && article.toc && article.toc.length > 0 && (
145
179
  <ArticleTOC toc={article.toc} />
146
180
  )}
@@ -169,10 +203,43 @@ export async function generateMetadata({ params }: CategoryPageProps) {
169
203
  }
170
204
  export default async function Page({ params }: CategoryPageProps) {
171
205
  const { category } = await params
172
- const articles = await getArticlesByCategory(category)
206
+ const articles = await getArticlesByCategory(category, siteConfig)
173
207
  if (articles.length === 0) notFound()
174
208
  return <CategoryArticlesPage category={category} articles={articles} config={siteConfig} />
175
209
  }
210
+
211
+ // app/articles/authors/[author]/page.tsx
212
+ import { notFound } from 'next/navigation'
213
+ import { AuthorArticlesPage, AuthorDetailHero, Breadcrumb } from '@fullstackdatasolutions/articles'
214
+ import {
215
+ buildAuthorBreadcrumbs,
216
+ generateAuthorMetadata,
217
+ generateAuthorStaticParams,
218
+ getArticlesByAuthor,
219
+ getAuthorBySlug,
220
+ } from '@fullstackdatasolutions/articles/server'
221
+ import { siteConfig } from '@/config/articles'
222
+ type AuthorPageProps = Readonly<{ params: Promise<{ author: string }> }>
223
+ export function generateStaticParams(): { author: string }[] {
224
+ return generateAuthorStaticParams(siteConfig)
225
+ }
226
+ export async function generateMetadata({ params }: AuthorPageProps) {
227
+ const { author } = await params
228
+ return generateAuthorMetadata(author, siteConfig)
229
+ }
230
+ export default async function Page({ params }: AuthorPageProps) {
231
+ const { author: authorSlug } = await params
232
+ const author = getAuthorBySlug(authorSlug, siteConfig)
233
+ if (!author) notFound()
234
+ const articles = await getArticlesByAuthor(authorSlug, siteConfig)
235
+ return (
236
+ <>
237
+ <Breadcrumb items={buildAuthorBreadcrumbs(author, siteConfig)} />
238
+ <AuthorDetailHero author={author} />
239
+ <AuthorArticlesPage author={author} articles={articles} config={siteConfig} />
240
+ </>
241
+ )
242
+ }
176
243
  ```
177
244
 
178
245
  ### 6. Set up markdown handler (Next.js 16+)
@@ -246,7 +313,7 @@ Create `public/articles/[slug]/article.md` or `public/articles/[slug]/article.md
246
313
  ---
247
314
  title: Your Article Title
248
315
  excerpt: A one-sentence summary shown in cards and meta descriptions.
249
- author: Jane Doe
316
+ authors: [jane-doe]
250
317
  tags: [campaigns, strategy]
251
318
  ---
252
319
 
@@ -255,6 +322,34 @@ Article body in Markdown...
255
322
 
256
323
  Article directories can be nested to any depth below `public/articles`. For example, `public/articles/game-system/article-name/article.mdx` is discovered as the slug `game-system/article-name`, and `public/articles/tov/subdirectory/article-name/article.md` is discovered as `tov/subdirectory/article-name`. Use a catch-all Next.js route such as `app/articles/[...slug]/page.tsx` and join the slug segments before calling the server helpers.
257
324
 
325
+ Articles can have zero, one, or many authors:
326
+
327
+ ```markdown
328
+ ---
329
+ title: Team Update
330
+ excerpt: A post with no visible author.
331
+ authors: []
332
+ ---
333
+ ```
334
+
335
+ ```markdown
336
+ ---
337
+ title: Joint Post
338
+ excerpt: A post with multiple authors.
339
+ authors:
340
+ - jane-doe
341
+ - alex-smith
342
+ ---
343
+ ```
344
+
345
+ The legacy `author: Jane Doe` string still works. Resolution order is:
346
+
347
+ 1. `authors: [...]` frontmatter
348
+ 2. `author: ...` frontmatter
349
+ 3. `defaultAuthor` from `ArticlesConfig`
350
+
351
+ Use `authors: []` when an article should intentionally show no author, even if `defaultAuthor` is configured.
352
+
258
353
  ---
259
354
 
260
355
  ## Markdown handler exports
@@ -340,27 +435,119 @@ Use this if you don't need a custom API base path. For custom paths, use `create
340
435
 
341
436
  ## `ArticlesConfig` reference
342
437
 
343
- | Field | Type | Default | Description |
344
- | ---------------------- | --------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
345
- | `siteUrl` | `string` | — | Canonical base URL. Used in metadata and JSON-LD. |
346
- | `siteName` | `string` | — | Site name in title tags and JSON-LD. |
347
- | `pageSize` | `number` | `6` | Articles per page / per "Load more" click. |
348
- | `categoriesPageSize` | `number` | `8` | Category cards before "Load more categories". |
349
- | `layout` | `ArticlesSection[]` | see below | Ordered sections to render. Omit a key to hide it. |
350
- | `theme` | `ArticlesTheme` | — | CSS custom-property overrides for colors and fonts. |
351
- | `categoryDescriptions` | `Record<string, string \| CategoryDescription>` | — | Short/long text per category slug. |
352
- | `hero` | `HeroConfig` | — | Hero section title and description. Omit to use built-in defaults. |
353
- | `comments` | `CommentsConfig` | — | Comments feature config. Omit to disable entirely. |
354
- | `showToc` | `boolean` | `true` | Show the table of contents on article detail pages. Set to `false` to hide on all articles. |
355
- | `showBackToArticles` | `boolean` | `true` | Show the back-navigation link on article detail pages. Set to `false` to hide `ArticleBackLink`. |
356
- | `showAuthor` | `boolean` | `true` | Show author names in UI and metadata. Set to `false` to omit author display, OpenGraph authors, JSON-LD author fields, and RSS authors. |
357
- | `linkTargetStrategy` | `'external-new-tab' \| 'all-new-tab' \| 'same-tab'` | `'external-new-tab'` | Controls article body links. Internal links open in the same window by default; external `http`/`https` links open in a new window. |
358
- | `description` | `string` | | Short description used as the RSS feed channel description. Falls back to `siteName` if omitted. |
438
+ | Field | Type | Default | Description |
439
+ | ---------------------- | --------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
440
+ | `siteUrl` | `string` | — | Canonical base URL. Used in metadata and JSON-LD. |
441
+ | `siteName` | `string` | — | Site name in title tags and JSON-LD. |
442
+ | `pageSize` | `number` | `6` | Articles per page / per "Load more" click. |
443
+ | `categoriesPageSize` | `number` | `8` | Category cards before "Load more categories". |
444
+ | `layout` | `ArticlesSection[]` | see below | Ordered sections to render. Omit a key to hide it. |
445
+ | `theme` | `ArticlesTheme` | — | CSS custom-property overrides for colors and fonts. |
446
+ | `categoryDescriptions` | `Record<string, string \| CategoryDescription>` | — | Short/long text per category slug. |
447
+ | `hero` | `HeroConfig` | — | Hero section title and description. Omit to use built-in defaults. |
448
+ | `comments` | `CommentsConfig` | — | Comments feature config. Omit to disable entirely. |
449
+ | `showToc` | `boolean` | `true` | Show the table of contents on article detail pages. Set to `false` to hide on all articles. |
450
+ | `showBackToArticles` | `boolean` | `true` | Show the back-navigation link on article detail pages. Set to `false` to hide `ArticleBackLink`. |
451
+ | `showAuthor` | `boolean` | `true` | Show author names in UI and metadata. Set to `false` to omit author display, OpenGraph authors, JSON-LD author fields, and RSS authors. |
452
+ | `showAuthorPage` | `boolean` | `true` | Include configured author pages in sitemap output and enable author page helpers. Set to `false` to suppress author page generation. |
453
+ | `authors` | `Record<string, AuthorProfile>` | `{}` | Configured author profiles keyed by slug. Slugs are used by frontmatter, author URLs, and JSON-LD `Person` data. |
454
+ | `defaultAuthor` | `string` | — | Default author slug or name used when article frontmatter omits both `authors` and `author`. |
455
+ | `breadcrumbs` | `false \| BreadcrumbsConfig` | default trails | Set to `false` to hide breadcrumbs, or configure article/category/author breadcrumb trails, custom URL items, separator, schema output, and labels. |
456
+ | `linkTargetStrategy` | `'external-new-tab' \| 'all-new-tab' \| 'same-tab'` | `'external-new-tab'` | Controls article body links. Internal links open in the same window by default; external `http`/`https` links open in a new window. |
457
+ | `mdxComponents` | `Record<string, ComponentType<never>>` | - | Components exposed to article `.mdx` bodies by tag name. Merged with the built-in MDX image override. |
458
+ | `description` | `string` | — | Short description used as the RSS feed channel description. Falls back to `siteName` if omitted. |
359
459
 
360
460
  **Default layout order:** `['hero', 'search', 'featured', 'latest', 'categories']`
361
461
 
362
462
  To add a newsletter section, pass `layout: ['hero','search','featured','latest','categories','newsletter']` — the library renders `null` for `'newsletter'` so you can append your own component after `<ArticlesPage />`.
363
463
 
464
+ ### Custom MDX components
465
+
466
+ Pass React components through `mdxComponents` to make them available to article `.mdx` files. The component key is the JSX tag authors write in the article.
467
+
468
+ ```tsx
469
+ // config/articles.ts
470
+ import type { ArticlesConfig } from '@fullstackdatasolutions/articles'
471
+ import { LeadMagnetCTA } from '@/components/LeadMagnetCTA'
472
+
473
+ export const siteConfig: ArticlesConfig = {
474
+ siteUrl: 'https://yoursite.com',
475
+ siteName: 'Your Site',
476
+ mdxComponents: {
477
+ LeadMagnetCTA,
478
+ },
479
+ }
480
+ ```
481
+
482
+ ```mdx
483
+ Article intro paragraph.
484
+
485
+ <LeadMagnetCTA system="DND" segment="new-players" />
486
+ ```
487
+
488
+ Imports inside individual `.mdx` article files are not supported. Define components in the consuming app and register them through `mdxComponents`.
489
+
490
+ ### Authors
491
+
492
+ Author profiles are optional. If an article has no configured author and no plain `author` frontmatter, author UI and author metadata are omitted for that article.
493
+
494
+ ```ts
495
+ authors: {
496
+ 'jane-doe': {
497
+ slug: 'jane-doe',
498
+ name: 'Jane Doe',
499
+ bio: 'Jane writes about campaign operations and civic technology.',
500
+ avatar: '/authors/jane-doe.jpg',
501
+ url: 'https://example.com/team/jane-doe',
502
+ social: {
503
+ website: 'https://example.com',
504
+ facebook: 'janedoe',
505
+ twitter: '@janedoe',
506
+ x: 'janedoe',
507
+ linkedin: 'https://www.linkedin.com/in/janedoe',
508
+ instagram: 'janedoe',
509
+ youtube: '@janedoe',
510
+ tiktok: '@janedoe',
511
+ github: 'janedoe',
512
+ bluesky: 'janedoe.bsky.social',
513
+ threads: 'janedoe',
514
+ mastodon: 'https://mastodon.social/@janedoe',
515
+ medium: 'janedoe',
516
+ newsletter: 'https://example.com/newsletter',
517
+ other: {
518
+ Podcast: 'https://example.com/podcast',
519
+ },
520
+ },
521
+ },
522
+ }
523
+ ```
524
+
525
+ Only social fields with values are rendered. Handle-style values such as `twitter: '@janedoe'` and `linkedin: 'janedoe'` are normalized to full profile URLs; absolute URLs are left unchanged. `social.other` lets you add custom social or profile links with your own labels.
526
+
527
+ ### Breadcrumbs
528
+
529
+ Breadcrumbs are visible by default. Disable them with `breadcrumbs: false`, or customize an article trail:
530
+
531
+ ```ts
532
+ breadcrumbs: {
533
+ separator: '/',
534
+ showSchema: true,
535
+ article: [
536
+ { name: 'Resources', url: '/resources' },
537
+ 'primaryCategory',
538
+ 'folderPath',
539
+ { name: 'Campaign Library', url: 'https://example.com/library' },
540
+ 'articleTitle',
541
+ ],
542
+ category: ['home', { name: 'Resources', url: '/resources' }, 'category'],
543
+ author: ['home', 'articles', { name: 'Team', url: '/team' }, 'authorName'],
544
+ }
545
+ ```
546
+
547
+ Breadcrumb trails can mix built-in tokens with custom URL items shaped as `{ name: string, url: string }`. Relative custom URLs beginning with `/` are resolved against `siteUrl`; absolute `http` and `https` URLs are left unchanged. The `folderPath` article token expands the article slug folders. For `public/articles/guides/field/example/article.md`, the trail above renders `Resources`, the primary tag, `Guides`, `Field`, `Campaign Library`, and the article title.
548
+
549
+ Use `breadcrumbs: { show: false }` to disable both visible breadcrumbs and breadcrumb JSON-LD while keeping the rest of the breadcrumb config nearby. Use `showSchema: false` to keep visible breadcrumbs but omit breadcrumb JSON-LD.
550
+
364
551
  ---
365
552
 
366
553
  ## Theming
@@ -739,7 +926,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
739
926
 
740
927
  Returns article entries (priority 0.8, changeFrequency 'weekly') and category entries (priority 0.7, changeFrequency 'weekly'). Alternative: pass a plain URL string instead of `siteConfig`: `getArticleSitemapEntries('https://yoursite.com')`.
741
928
 
742
- JSON-LD structured data (`ArticleSchema`, `BreadcrumbSchema`, `CollectionPageSchema`) is rendered automatically inside the library components no extra wiring required.
929
+ JSON-LD structured data (`ArticleSchema`, `Breadcrumb`, `BreadcrumbSchema`, `CollectionPageSchema`) is rendered automatically inside the library components. Use `Breadcrumb` for visible navigation plus schema, or `BreadcrumbSchema` when you only need visually hidden breadcrumb schema output.
743
930
 
744
931
  ---
745
932
 
@@ -749,7 +936,8 @@ JSON-LD structured data (`ArticleSchema`, `BreadcrumbSchema`, `CollectionPageSch
749
936
  | --------------- | ---------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
750
937
  | `title` | `string` | yes | Article title. If omitted, falls back to the article slug with dashes replaced by spaces. |
751
938
  | `excerpt` | `string` | yes | One-sentence summary for cards and meta. If omitted, resolves to an empty string. |
752
- | `author` | `string` | no | Defaults to `'Andrew Blase'` |
939
+ | `author` | `string` | no | Legacy single author slug or display name. Used when `authors` is omitted. Falls back to `defaultAuthor` only when configured. |
940
+ | `authors` | `string[]` | no | Preferred multi-author list. Values should match `ArticlesConfig.authors` keys; unknown values still render as fallback author names. Use `authors: []` for an explicitly authorless article. |
753
941
  | `tags` | `string[]` | no | Used as categories. First tag = primary category. ArticleDetailHero displays at most 4 category tags; articles with more than 4 show only the first 4 in the hero. |
754
942
  | `date` | `YYYY-MM-DD` | no | Omit to publish immediately. Future dates hide until that date. |
755
943
  | `lastmod` | `YYYY-MM-DD` | no | Last modified date used by sitemap metadata when available. |
@@ -776,6 +964,7 @@ import {
776
964
  ArticleTOC,
777
965
  getAllArticles,
778
966
  getArticleMetadata,
967
+ getArticleAuthors,
779
968
  getArticleMarkdown,
780
969
  getArticleMarkdownResponse,
781
970
  getArticleMarkdownUrl,
@@ -783,6 +972,9 @@ import {
783
972
  getAiRobotsTxtRules,
784
973
  setArticlesErrorHandler,
785
974
  getArticlesByCategory,
975
+ getAuthorBySlug,
976
+ getAllAuthors,
977
+ getArticlesByAuthor,
786
978
  getAllCategories,
787
979
  getAvailableArticleSlugs,
788
980
  getAdjacentArticles,
@@ -793,11 +985,16 @@ import {
793
985
  extractToc,
794
986
  generateArticleStaticParams,
795
987
  generateCategoryStaticParams,
988
+ generateAuthorStaticParams,
796
989
  getArticleSitemapEntries,
797
990
  generateRssFeed,
798
991
  generateArticlesIndexMetadata,
799
992
  generateArticleMetadata,
800
993
  generateCategoryMetadata,
994
+ generateAuthorMetadata,
995
+ buildArticleBreadcrumbs,
996
+ buildCategoryBreadcrumbs,
997
+ buildAuthorBreadcrumbs,
801
998
  } from '@fullstackdatasolutions/articles/server'
802
999
  ```
803
1000