@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.
- package/CHANGELOG.md +243 -0
- package/README.md +226 -29
- package/dist/index.cjs +635 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -56
- package/dist/index.d.ts +165 -56
- package/dist/index.js +614 -250
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +40 -5
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +72 -0
- package/dist/nextjs.d.ts +72 -0
- package/dist/nextjs.js +40 -5
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +280 -16
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +92 -3
- package/dist/server.d.ts +92 -3
- package/dist/server.js +269 -16
- package/dist/server.js.map +1 -1
- package/package.json +8 -5
- package/src/ArticleDetailHero.tsx +27 -2
- package/src/ArticleSchemas.tsx +27 -27
- package/src/AuthorArticlesPage.tsx +60 -0
- package/src/AuthorCard.tsx +112 -0
- package/src/AuthorDetailHero.tsx +56 -0
- package/src/Breadcrumb.tsx +78 -0
- package/src/CategoryArticlesPage.tsx +62 -11
- package/src/__tests__/ArticleDetailHero.test.tsx +21 -1
- package/src/__tests__/ArticleSchemas.test.tsx +47 -2
- package/src/__tests__/AuthorArticlesPage.test.tsx +74 -0
- package/src/__tests__/AuthorCard.test.tsx +98 -0
- package/src/__tests__/AuthorDetailHero.test.tsx +51 -0
- package/src/__tests__/CategoryArticlesPage.test.tsx +31 -5
- package/src/__tests__/articlesConfig.test.ts +20 -1
- package/src/__tests__/authorUtils.test.ts +89 -0
- package/src/__tests__/renderMdx.test.tsx +113 -0
- package/src/__tests__/seoUtils-authors.test.ts +160 -0
- package/src/__tests__/seoUtils.test.ts +4 -0
- package/src/__tests__/server-articles.test.ts +159 -2
- package/src/articleTypes.ts +33 -0
- package/src/articlesConfig.ts +68 -0
- package/src/authorUtils.ts +95 -0
- package/src/index.ts +32 -9
- package/src/renderMdx.tsx +8 -2
- package/src/seoUtils.ts +226 -7
- package/src/server-articles.ts +98 -10
- 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
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
358
|
-
| `
|
|
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
|
|
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 |
|
|
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
|
|