@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 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 { generateArticlesIndexMetadata } from '@fullstackdatasolutions/articles/server'
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
- return <ArticlesPage config={siteConfig} />
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)(true);
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({ config }) {
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;