@fullstackdatasolutions/articles 0.11.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 +6 -0
- package/README.md +18 -3
- package/dist/index.cjs +11 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +11 -7
- package/dist/index.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__/useArticles.test.ts +28 -0
- package/src/useArticles.ts +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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
|
+
|
|
8
14
|
## [0.11.0] - 2026-07-30
|
|
9
15
|
|
|
10
16
|
### 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
|
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;
|