@fullstackdatasolutions/articles 0.10.0 → 0.12.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 +12 -0
- package/README.md +45 -3
- package/dist/index.cjs +11 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/nextjs.d.cts +5 -0
- package/dist/nextjs.d.ts +5 -0
- package/dist/server.cjs +2 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/ArticlesPage.tsx +10 -2
- package/src/__tests__/ArticlesPage.test.tsx +17 -0
- package/src/__tests__/articlesConfig.test.ts +20 -1
- package/src/__tests__/renderMdx.test.tsx +78 -0
- package/src/__tests__/useArticles.test.ts +28 -0
- package/src/articlesConfig.ts +6 -0
- package/src/index.ts +1 -0
- package/src/renderMdx.tsx +5 -1
- package/src/useArticles.ts +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.12.0] - 2026-08-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **SEO: `ArticlesPage` no longer requires client-side JS to expose article links.** Previously `ArticlesPage` fetched its article list entirely client-side via `useArticles()`, so the server-rendered HTML for `/articles` contained only a "Loading articles..." placeholder with zero `<a href>` links — crawlers that don't execute JS (or that snapshot the initial response) never discovered any article URLs from the index page. `ArticlesPage` now accepts optional `initialArticles`/`initialCategories` props; when a consuming app passes server-fetched data (e.g. via `getAllArticles()`/`getAllCategories()` from `@fullstackdatasolutions/articles/server` in an `async` Server Component), the first render is fully populated with real links and no client fetch occurs on mount. This matches the pattern `CategoryArticlesPage` and `AuthorArticlesPage` already used. Omitting the new props preserves the previous client-fetch behavior, so this is backward compatible.
|
|
13
|
+
|
|
14
|
+
## [0.11.0] - 2026-07-30
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- 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.
|
|
19
|
+
|
|
8
20
|
## [0.10.0] - 2026-07-23
|
|
9
21
|
|
|
10
22
|
### Added
|
package/README.md
CHANGED
|
@@ -110,11 +110,26 @@ export const siteConfig: ArticlesConfig = {
|
|
|
110
110
|
// app/articles/page.tsx
|
|
111
111
|
import type { Metadata } from 'next'
|
|
112
112
|
import { ArticlesPage } from '@fullstackdatasolutions/articles'
|
|
113
|
-
import {
|
|
113
|
+
import {
|
|
114
|
+
generateArticlesIndexMetadata,
|
|
115
|
+
getAllArticles,
|
|
116
|
+
getAllCategories,
|
|
117
|
+
} from '@fullstackdatasolutions/articles/server'
|
|
114
118
|
import { siteConfig } from '@/config/articles'
|
|
115
119
|
export const metadata: Metadata = generateArticlesIndexMetadata(siteConfig)
|
|
116
|
-
export default function Page() {
|
|
117
|
-
|
|
120
|
+
export default async function Page() {
|
|
121
|
+
// Fetching server-side and passing as initialArticles/initialCategories means the
|
|
122
|
+
// first response already contains real <a href> article links — required for
|
|
123
|
+
// crawlers that don't execute client JS. Omitting these props still works, but
|
|
124
|
+
// falls back to a client-side fetch and a "Loading articles..." placeholder in
|
|
125
|
+
// the initial HTML.
|
|
126
|
+
const [articles, categories] = await Promise.all([
|
|
127
|
+
getAllArticles(siteConfig),
|
|
128
|
+
getAllCategories(),
|
|
129
|
+
])
|
|
130
|
+
return (
|
|
131
|
+
<ArticlesPage config={siteConfig} initialArticles={articles} initialCategories={categories} />
|
|
132
|
+
)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
// app/articles/[...slug]/page.tsx
|
|
@@ -454,12 +469,39 @@ Use this if you don't need a custom API base path. For custom paths, use `create
|
|
|
454
469
|
| `defaultAuthor` | `string` | — | Default author slug or name used when article frontmatter omits both `authors` and `author`. |
|
|
455
470
|
| `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
471
|
| `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. |
|
|
472
|
+
| `mdxComponents` | `Record<string, ComponentType<never>>` | - | Components exposed to article `.mdx` bodies by tag name. Merged with the built-in MDX image override. |
|
|
457
473
|
| `description` | `string` | — | Short description used as the RSS feed channel description. Falls back to `siteName` if omitted. |
|
|
458
474
|
|
|
459
475
|
**Default layout order:** `['hero', 'search', 'featured', 'latest', 'categories']`
|
|
460
476
|
|
|
461
477
|
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 />`.
|
|
462
478
|
|
|
479
|
+
### Custom MDX components
|
|
480
|
+
|
|
481
|
+
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.
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
// config/articles.ts
|
|
485
|
+
import type { ArticlesConfig } from '@fullstackdatasolutions/articles'
|
|
486
|
+
import { LeadMagnetCTA } from '@/components/LeadMagnetCTA'
|
|
487
|
+
|
|
488
|
+
export const siteConfig: ArticlesConfig = {
|
|
489
|
+
siteUrl: 'https://yoursite.com',
|
|
490
|
+
siteName: 'Your Site',
|
|
491
|
+
mdxComponents: {
|
|
492
|
+
LeadMagnetCTA,
|
|
493
|
+
},
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```mdx
|
|
498
|
+
Article intro paragraph.
|
|
499
|
+
|
|
500
|
+
<LeadMagnetCTA system="DND" segment="new-players" />
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Imports inside individual `.mdx` article files are not supported. Define components in the consuming app and register them through `mdxComponents`.
|
|
504
|
+
|
|
463
505
|
### Authors
|
|
464
506
|
|
|
465
507
|
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.
|
package/dist/index.cjs
CHANGED
|
@@ -715,12 +715,12 @@ function getBreadcrumbsConfig(config) {
|
|
|
715
715
|
|
|
716
716
|
// src/useArticles.ts
|
|
717
717
|
var import_react3 = require("react");
|
|
718
|
-
function useArticles() {
|
|
718
|
+
function useArticles(initialArticles, initialCategories) {
|
|
719
719
|
const [searchQuery, setSearchQuery] = (0, import_react3.useState)("");
|
|
720
720
|
const debounceRef = (0, import_react3.useRef)(null);
|
|
721
|
-
const [articles, setArticles] = (0, import_react3.useState)([]);
|
|
722
|
-
const [categories, setCategories] = (0, import_react3.useState)([]);
|
|
723
|
-
const [loading, setLoading] = (0, import_react3.useState)(
|
|
721
|
+
const [articles, setArticles] = (0, import_react3.useState)(initialArticles != null ? initialArticles : []);
|
|
722
|
+
const [categories, setCategories] = (0, import_react3.useState)(initialCategories != null ? initialCategories : []);
|
|
723
|
+
const [loading, setLoading] = (0, import_react3.useState)(initialArticles === void 0);
|
|
724
724
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
725
725
|
const fetchArticles = (0, import_react3.useCallback)((query) => __async(this, null, function* () {
|
|
726
726
|
try {
|
|
@@ -758,7 +758,7 @@ function useArticles() {
|
|
|
758
758
|
}
|
|
759
759
|
}), []);
|
|
760
760
|
(0, import_react3.useEffect)(() => {
|
|
761
|
-
fetchArticles("");
|
|
761
|
+
if (initialArticles === void 0) fetchArticles("");
|
|
762
762
|
}, [fetchArticles]);
|
|
763
763
|
const handleSearch = (0, import_react3.useCallback)(
|
|
764
764
|
(query) => {
|
|
@@ -889,9 +889,13 @@ function renderSection(section, ctx, config) {
|
|
|
889
889
|
return null;
|
|
890
890
|
}
|
|
891
891
|
}
|
|
892
|
-
function ArticlesPage({
|
|
892
|
+
function ArticlesPage({
|
|
893
|
+
config,
|
|
894
|
+
initialArticles,
|
|
895
|
+
initialCategories
|
|
896
|
+
}) {
|
|
893
897
|
var _a, _b, _c;
|
|
894
|
-
const state = useArticles();
|
|
898
|
+
const state = useArticles(initialArticles, initialCategories);
|
|
895
899
|
const layout = (_a = config.layout) != null ? _a : DEFAULT_LAYOUT;
|
|
896
900
|
const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
|
|
897
901
|
const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
|