@fecommunity/reactpress-theme-starter 1.0.0-beta.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 (163) hide show
  1. package/.env.example +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +286 -0
  4. package/app/api/[[...path]]/route.ts +69 -0
  5. package/app/archives/page.tsx +28 -0
  6. package/app/article/[id]/page.tsx +92 -0
  7. package/app/category/[category]/page.tsx +60 -0
  8. package/app/globals.css +4969 -0
  9. package/app/knowledge/[pId]/[id]/page.tsx +54 -0
  10. package/app/knowledge/[pId]/page.tsx +47 -0
  11. package/app/knowledge/page.tsx +29 -0
  12. package/app/layout.tsx +76 -0
  13. package/app/llms.txt/route.ts +26 -0
  14. package/app/login/page.tsx +17 -0
  15. package/app/nav/[id]/page.tsx +44 -0
  16. package/app/nav/page.tsx +5 -0
  17. package/app/not-found.tsx +21 -0
  18. package/app/page/[id]/page.tsx +57 -0
  19. package/app/page.tsx +61 -0
  20. package/app/register/page.tsx +17 -0
  21. package/app/robots.ts +14 -0
  22. package/app/rss/route.ts +82 -0
  23. package/app/search/page.tsx +46 -0
  24. package/app/sitemap.ts +38 -0
  25. package/app/suggestions/page.tsx +47 -0
  26. package/app/tag/[tag]/page.tsx +58 -0
  27. package/app/tags/page.tsx +18 -0
  28. package/app/template.tsx +6 -0
  29. package/components/article/ArchiveBanner.tsx +40 -0
  30. package/components/article/ArticleActionsBar.tsx +53 -0
  31. package/components/article/ArticleCarousel.tsx +237 -0
  32. package/components/article/ArticleCopyrightBar.tsx +128 -0
  33. package/components/article/ArticleEndBar.tsx +19 -0
  34. package/components/article/ArticleFeedSection.tsx +59 -0
  35. package/components/article/ArticleList.tsx +170 -0
  36. package/components/article/ArticleReadingChrome.tsx +104 -0
  37. package/components/article/ArticleReadingProgress.tsx +75 -0
  38. package/components/article/ArticleRecommend.tsx +362 -0
  39. package/components/article/ArticleTocPanel.tsx +19 -0
  40. package/components/article/ArticleViewClient.tsx +221 -0
  41. package/components/article/CategoryMenu.tsx +67 -0
  42. package/components/article/CmsPageClient.tsx +92 -0
  43. package/components/article/LoadMore.tsx +59 -0
  44. package/components/article/ReadingContent.tsx +51 -0
  45. package/components/auth/AuthPageClient.tsx +314 -0
  46. package/components/comment/CommentEditor.tsx +290 -0
  47. package/components/comment/CommentEmoji.tsx +110 -0
  48. package/components/comment/CommentIconButton.tsx +37 -0
  49. package/components/comment/CommentItem.tsx +135 -0
  50. package/components/comment/CommentSection.tsx +123 -0
  51. package/components/comment/emojis.ts +152 -0
  52. package/components/knowledge/KnowledgeList.tsx +70 -0
  53. package/components/layout/BackToTop.tsx +27 -0
  54. package/components/layout/ConditionalSiteFooter.tsx +29 -0
  55. package/components/layout/ContentStylesLoader.tsx +9 -0
  56. package/components/layout/DoubleColumnLayout.tsx +171 -0
  57. package/components/layout/HeaderSearch.tsx +23 -0
  58. package/components/layout/LocaleSwitcher.tsx +24 -0
  59. package/components/layout/PageContainer.tsx +11 -0
  60. package/components/layout/PageLoadProgress.tsx +169 -0
  61. package/components/layout/SiteFooter.tsx +75 -0
  62. package/components/layout/SiteHeader.tsx +312 -0
  63. package/components/layout/SystemNotification.tsx +176 -0
  64. package/components/layout/ThemeSwitch.tsx +19 -0
  65. package/components/layout/UserAuth.tsx +130 -0
  66. package/components/search/SearchHero.tsx +323 -0
  67. package/components/search/SearchQuickLinks.tsx +285 -0
  68. package/components/shared/DevChunkRecovery.tsx +8 -0
  69. package/components/shared/Link.tsx +30 -0
  70. package/components/shared/SectionHeading.tsx +38 -0
  71. package/components/shared/ThemeImage.tsx +13 -0
  72. package/components/shared/logo.svg +7 -0
  73. package/components/views/ArchivesClient.tsx +137 -0
  74. package/components/views/CategoryClient.tsx +87 -0
  75. package/components/views/HomeClient.tsx +79 -0
  76. package/components/views/KnowledgeBookClient.tsx +127 -0
  77. package/components/views/KnowledgeChapterClient.tsx +175 -0
  78. package/components/views/KnowledgeClient.tsx +69 -0
  79. package/components/views/LoginClient.tsx +80 -0
  80. package/components/views/NavDetailClient.tsx +177 -0
  81. package/components/views/SearchClient.tsx +100 -0
  82. package/components/views/TagArchiveClient.tsx +105 -0
  83. package/components/views/TagsClient.tsx +46 -0
  84. package/components/widgets/AboutUs.tsx +180 -0
  85. package/components/widgets/ArticleActionButton.tsx +95 -0
  86. package/components/widgets/BookmarkWidget.tsx +61 -0
  87. package/components/widgets/CategoriesWidget.tsx +47 -0
  88. package/components/widgets/HomeSidebar.tsx +40 -0
  89. package/components/widgets/LikesWidget.tsx +77 -0
  90. package/components/widgets/ShareWidget.tsx +120 -0
  91. package/components/widgets/TagCloud.tsx +94 -0
  92. package/components/widgets/TagsWidget.tsx +160 -0
  93. package/eslint.config.mjs +80 -0
  94. package/lib/auth/githubOAuth.ts +26 -0
  95. package/lib/auth/sessionUser.ts +41 -0
  96. package/lib/mock-api/auth.ts +129 -0
  97. package/lib/mock-api/config.ts +11 -0
  98. package/lib/mock-api/cors.ts +53 -0
  99. package/lib/mock-api/data.ts +998 -0
  100. package/lib/mock-api/match-route.ts +202 -0
  101. package/lib/mock-api/proxy.ts +28 -0
  102. package/lib/providers/client.ts +58 -0
  103. package/lib/providers/materializeProvider.ts +9 -0
  104. package/lib/providers/server.ts +8 -0
  105. package/lib/reactpress/appearance.ts +69 -0
  106. package/lib/reactpress/bootstrap.ts +48 -0
  107. package/lib/reactpress/coercePublishedPage.ts +12 -0
  108. package/lib/reactpress/contentSeo.ts +123 -0
  109. package/lib/reactpress/env.ts +31 -0
  110. package/lib/reactpress/feedFooterPlacement.ts +16 -0
  111. package/lib/reactpress/fetchSetting.ts +19 -0
  112. package/lib/reactpress/homeFooter.tsx +27 -0
  113. package/lib/reactpress/layout-shell.tsx +19 -0
  114. package/lib/reactpress/materializeBootstrapCatalog.ts +31 -0
  115. package/lib/reactpress/normalizeApiResponse.ts +25 -0
  116. package/lib/reactpress/providers.tsx +199 -0
  117. package/lib/reactpress/resolveSiteNav.ts +56 -0
  118. package/lib/reactpress/seoMetadata.ts +82 -0
  119. package/lib/reactpress/serverLocale.ts +43 -0
  120. package/lib/reactpress/siteMetadata.ts +151 -0
  121. package/lib/reactpress/staticParams.ts +46 -0
  122. package/lib/reactpress/suggestionsContent.ts +48 -0
  123. package/lib/reactpress/useFeedFooterPlacement.ts +22 -0
  124. package/lib/reactpress/useSiteNavConfig.ts +39 -0
  125. package/lib/utils/archiveBanner.ts +8 -0
  126. package/lib/utils/colors.ts +119 -0
  127. package/lib/utils/comment.ts +46 -0
  128. package/lib/utils/headerPosition.ts +55 -0
  129. package/lib/utils/icons.tsx +238 -0
  130. package/lib/utils/reading.ts +16 -0
  131. package/lib/utils/scroll.ts +8 -0
  132. package/lib/utils/tagCloud.ts +222 -0
  133. package/next.config.js +103 -0
  134. package/package.json +98 -0
  135. package/postcss.config.js +5 -0
  136. package/prettier.config.js +10 -0
  137. package/public/apple-touch-icon.png +0 -0
  138. package/public/cli.png +0 -0
  139. package/public/favicon-32.png +0 -0
  140. package/public/favicon.ico +0 -0
  141. package/public/home-dark.png +0 -0
  142. package/public/home-light.png +0 -0
  143. package/public/icon-192.png +0 -0
  144. package/public/icon-512.png +0 -0
  145. package/public/logo-400.png +0 -0
  146. package/public/logo.png +0 -0
  147. package/public/mobile.png +0 -0
  148. package/public/pad.png +0 -0
  149. package/public/site.webmanifest +19 -0
  150. package/scripts/build-mock.mjs +139 -0
  151. package/scripts/dev.mjs +221 -0
  152. package/scripts/ensure-env.mjs +100 -0
  153. package/scripts/remote-dev.mjs +143 -0
  154. package/scripts/resolve-api-env.mjs +114 -0
  155. package/scripts/set-publish-version.mjs +31 -0
  156. package/scripts/smoke-pages.mjs +113 -0
  157. package/scripts/start-mock.mjs +79 -0
  158. package/scripts/verify-toolkit.mjs +37 -0
  159. package/svg.d.ts +6 -0
  160. package/theme.json +123 -0
  161. package/theme.manifest.schema.json +265 -0
  162. package/tsconfig.json +41 -0
  163. package/vercel.json +12 -0
package/.env.example ADDED
@@ -0,0 +1,22 @@
1
+ # ReactPress API (SSR — Next.js server requests)
2
+ REACTPRESS_API_URL=http://localhost:3002/api
3
+
4
+ # Browser CSR requests (same as SSR, or use same-origin /api via rewrites)
5
+ NEXT_PUBLIC_REACTPRESS_API_URL=http://localhost:3002/api
6
+
7
+ # Public site URL (SEO / sitemap / Open Graph)
8
+ CLIENT_SITE_URL=http://localhost:3001
9
+
10
+ # Subpath deployment (optional)
11
+ # BASE_PATH=/blog
12
+
13
+ # ── Mock mode (offline demo / CI / Vercel starter deploy) ───────────────────
14
+ # REACTPRESS_MOCK_API=1
15
+
16
+ # ── Remote API during dev (equivalent to --remote-origin) ───────────────────
17
+ # REACTPRESS_DEV_REMOTE_ORIGIN=https://api.yoursite.com
18
+
19
+ # ── Production (Vercel / self-hosted) ───────────────────────────────────────
20
+ # REACTPRESS_API_URL=https://api.yoursite.com/api
21
+ # NEXT_PUBLIC_REACTPRESS_API_URL=/api
22
+ # CLIENT_SITE_URL=https://www.yoursite.com
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ReactPress / FECommunity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ <div align="center">
2
+
3
+ <a href="https://reactpress.surge.sh/">
4
+ <img src="./public/logo.png" alt="ReactPress" width="480" />
5
+ </a>
6
+
7
+ <br />
8
+
9
+ **ReactPress Theme Starter**
10
+
11
+ A lightweight, simple, and easy-to-use Next.js theme, powered by [ReactPress](https://github.com/fecommunity/reactpress).
12
+
13
+ <a href="https://reactpress-theme-starter.vercel.app">
14
+ <img src="./public/home-dark.png" alt="ReactPress Theme Starter preview" width="100%" />
15
+ </a>
16
+
17
+ <br />
18
+
19
+ [Live Demo](https://reactpress-theme-starter.vercel.app) · [Documentation](https://reactpress.surge.sh/) · [简体中文](./README_zh.md)
20
+
21
+ <br />
22
+
23
+ <img src="https://img.shields.io/badge/Next.js_15-000?style=flat-square&logo=next.js&logoColor=white" alt="Next.js 15" />
24
+ <img src="https://img.shields.io/badge/React_19-61dafb?style=flat-square&logo=react&logoColor=white" alt="React 19" />
25
+ <img src="https://img.shields.io/badge/Tailwind_4-38bdf8?style=flat-square&logo=tailwindcss&logoColor=white" alt="Tailwind CSS 4" />
26
+ <img src="https://img.shields.io/badge/TypeScript-0074c1?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" />
27
+ <img src="https://img.shields.io/badge/License-MIT-22c55e?style=flat-square" alt="MIT License" />
28
+ <a href="https://github.com/fecommunity/reactpress-theme-starter/stargazers">
29
+ <img src="https://img.shields.io/github/stars/fecommunity/reactpress-theme-starter?style=flat-square&logo=github" alt="GitHub stars" />
30
+ </a>
31
+
32
+ <br />
33
+
34
+ **If this project helps you, a ⭐ on GitHub helps others discover it.**
35
+
36
+ </div>
37
+
38
+ ---
39
+
40
+ ## Try in 60 seconds (no backend)
41
+
42
+ Preview the full theme UI with built-in sample data — no ReactPress install required.
43
+
44
+ ### Option A — `create-next-app` (recommended)
45
+
46
+ Bootstrap from this repository with the [official Next.js CLI](https://nextjs.org/docs/app/api-reference/cli/create-next-app):
47
+
48
+ ```bash
49
+ npx create-next-app@latest reactpress-theme-starter --example "https://github.com/fecommunity/reactpress-theme-starter" --use-pnpm
50
+ cd reactpress-theme-starter
51
+ pnpm dev:mock
52
+ ```
53
+
54
+ ### Option B — clone manually
55
+
56
+ ```bash
57
+ git clone https://github.com/fecommunity/reactpress-theme-starter.git
58
+ cd reactpress-theme-starter
59
+ pnpm install
60
+ pnpm dev:mock
61
+ ```
62
+
63
+ Open **http://localhost:3001** — same mode as the [live demo](https://reactpress-theme-starter.vercel.app).
64
+
65
+ ### Deploy to Vercel (zero env vars)
66
+
67
+ One-click deploy uses [`vercel.json`](./vercel.json) with mock data — no backend required.
68
+
69
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ffecommunity%2Freactpress-theme-starter&project-name=reactpress-theme&repository-name=reactpress-theme&demo-title=ReactPress%20Theme%20Starter&demo-description=Next.js%2015%20headless%20blog%20theme%20with%20mock%20mode%20%E2%80%94%20no%20backend%20required&demo-url=https%3A%2F%2Freactpress-theme-starter.vercel.app&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2Ffecommunity%2Freactpress-theme-starter%2Fmaster%2Fpublic%2Fhome-dark.png)
70
+
71
+ ---
72
+
73
+ ## What is this?
74
+
75
+ This repository is the **public-facing theme** for ReactPress: routing, layout, SEO, and UI. It does **not** include a CMS or admin panel — all content comes from a ReactPress backend via [`@fecommunity/reactpress-toolkit`](https://www.npmjs.com/package/@fecommunity/reactpress-toolkit).
76
+
77
+ ```text
78
+ ReactPress API ──REST──▶ Theme Starter (Next.js) ──▶ Public Site
79
+ ```
80
+
81
+ **Stack:** Next.js 15 · React 19 · Tailwind CSS 4 · Node.js 20+ · pnpm 9
82
+
83
+ **Includes:** articles, archives, search, CMS pages, knowledge base, comments, light/dark mode, RSS/sitemap, and an optional embedded mock API for offline development.
84
+
85
+ ### Why this theme?
86
+
87
+ | | ReactPress Theme Starter | Generic Next.js blog template |
88
+ | :------------------------ | :----------------------------------------------------------------------- | :---------------------------- |
89
+ | CMS & admin | ✅ [ReactPress](https://github.com/fecommunity/reactpress) backend + CLI | ❌ Build or wire your own |
90
+ | Try without backend | ✅ `pnpm dev:mock` | ❌ Usually needs content/API |
91
+ | Knowledge base & comments | ✅ Built-in | ❌ Roll your own |
92
+ | Customizable appearance | ✅ Admin console + `theme.json` | ❌ Hard-coded or manual |
93
+
94
+ ---
95
+
96
+ ## Quick Start
97
+
98
+ **Requirements:** Node.js 20+, pnpm 9+
99
+
100
+ ### Recommended — ReactPress CLI
101
+
102
+ The fastest way to run a full stack (visitor site, admin console, and API) is the [ReactPress CLI](https://reactpress.surge.sh/). No manual `.env` wiring for a standard setup.
103
+
104
+ ```bash
105
+ npm i -g @fecommunity/reactpress@3
106
+ reactpress init
107
+ reactpress dev
108
+ ```
109
+
110
+ <div align="center">
111
+
112
+ <img src="./public/cli.png" alt="ReactPress CLI interactive menu" width="100%" />
113
+
114
+ </div>
115
+
116
+ After startup, open the URLs printed in the terminal (typically the public site, admin, and API). Use `reactpress doctor` if something fails to start.
117
+
118
+ **Use this theme with the CLI**
119
+
120
+ 1. Clone this repository and install dependencies (`pnpm install`).
121
+ 2. Start the API with `reactpress dev --api-only` (or run the full stack and point the theme at the API).
122
+ 3. Copy [`.env.example`](./.env.example) to `.env` if needed — default local API is `http://localhost:3002/api`.
123
+ 4. In the theme directory, run:
124
+
125
+ ```bash
126
+ pnpm dev
127
+ ```
128
+
129
+ Open **http://localhost:3001** — the theme loads live content from your ReactPress API.
130
+
131
+ Remote API during development:
132
+
133
+ ```bash
134
+ pnpm dev -- --remote-origin api.yoursite.com
135
+ ```
136
+
137
+ ### Local theme preview (mock — no backend)
138
+
139
+ To work on UI, layout, or styling **without** installing ReactPress or running the API, use the built-in mock server:
140
+
141
+ ```bash
142
+ git clone https://github.com/fecommunity/reactpress-theme-starter.git
143
+ cd reactpress-theme-starter
144
+ pnpm install
145
+ pnpm dev:mock
146
+ ```
147
+
148
+ Open **http://localhost:3001** — sample data comes from [`lib/mock-api/data.ts`](./lib/mock-api/data.ts). This is the same mode used by the [Vercel demo](https://reactpress-theme-starter.vercel.app).
149
+
150
+ ---
151
+
152
+ ## Commands
153
+
154
+ | Command | Purpose |
155
+ | :------------------- | :------------------------------------------------------------ |
156
+ | `pnpm dev` | Theme dev server + live ReactPress API (CLI or remote) |
157
+ | `pnpm dev:mock` | Theme dev server + built-in mock API (no backend) |
158
+ | `pnpm build:mock` | Production build with mock data (Vercel demo / offline build) |
159
+ | `pnpm build` | Production build (API must be reachable at build time) |
160
+ | `pnpm start` | Run production server on port **3001** |
161
+ | `pnpm run check` | ESLint + Prettier |
162
+ | `pnpm run typecheck` | TypeScript check |
163
+
164
+ For full-stack local development (site + admin + API), prefer **`reactpress dev`** from the ReactPress CLI — see [Quick Start](#quick-start).
165
+
166
+ ---
167
+
168
+ ## Configuration
169
+
170
+ When using **`pnpm dev`** against a live API, copy [`.env.example`](./.env.example) to `.env`. Mock mode (`pnpm dev:mock`) and the ReactPress CLI handle most setup for you.
171
+
172
+ | Variable | Description |
173
+ | :------------------------------- | :---------------------------------------------- |
174
+ | `REACTPRESS_API_URL` | Server-side API base URL (include `/api`) |
175
+ | `NEXT_PUBLIC_REACTPRESS_API_URL` | Client-side API URL; use `/api` for same-origin |
176
+ | `CLIENT_SITE_URL` | Public site URL for SEO and Open Graph |
177
+ | `REACTPRESS_MOCK_API` | Set to `1` to enable the embedded mock API |
178
+
179
+ See `.env.example` for optional variables (subpath deploy, GitHub OAuth, remote dev origin).
180
+
181
+ ---
182
+
183
+ ## Deployment
184
+
185
+ ### Demo (Vercel)
186
+
187
+ Import this repo as-is — [`vercel.json`](./vercel.json) uses `build:mock` with mock data. See [Deploy to Vercel](#deploy-to-vercel-zero-env-vars) above for the one-click button with live demo preview.
188
+
189
+ > The [live demo](https://reactpress-theme-starter.vercel.app) runs in mock mode (UI + sample data only).
190
+
191
+ ### Production
192
+
193
+ Deploy as a headless frontend pointed at your ReactPress API:
194
+
195
+ 1. Set `REACTPRESS_API_URL`, `NEXT_PUBLIC_REACTPRESS_API_URL`, and `CLIENT_SITE_URL`
196
+ 2. Build with `pnpm build` (not `build:mock`)
197
+ 3. Start with `pnpm start` or your platform's Next.js runtime
198
+
199
+ `pnpm build` prefetches pages from the API — ensure the API is reachable at build time.
200
+
201
+ ---
202
+
203
+ ## Project Layout
204
+
205
+ ```text
206
+ app/ App Router pages, feeds, sitemap, mock API route
207
+ components/ Layout, article, search, widgets
208
+ lib/ mock-api/, reactpress/ utilities
209
+ scripts/ dev, build, and smoke-test scripts
210
+ theme.json Theme manifest (routes, appearance schema)
211
+ ```
212
+
213
+ Route templates are declared in [`theme.json`](./theme.json). Appearance (colors, logo, navigation) is configured in the ReactPress admin console.
214
+
215
+ ---
216
+
217
+ ## FAQ
218
+
219
+ <details>
220
+ <summary><strong>Do I need ReactPress to preview the theme?</strong></summary>
221
+
222
+ No. Run `pnpm dev:mock` for a full UI preview with sample data. Install [ReactPress CLI](https://reactpress.surge.sh/) when you want a real CMS, admin panel, and API.
223
+
224
+ </details>
225
+
226
+ <details>
227
+ <summary><strong>How is this different from WordPress or Ghost themes?</strong></summary>
228
+
229
+ This is a **headless frontend** — content lives in ReactPress (REST API + admin). You deploy the Next.js theme separately and point it at your API. See [ReactPress docs](https://reactpress.surge.sh/) for the full stack.
230
+
231
+ </details>
232
+
233
+ <details>
234
+ <summary><strong>Can I use my own Next.js components or fork the theme?</strong></summary>
235
+
236
+ Yes. MIT licensed. Customize components under `app/` and `components/`, or declare routes in [`theme.json`](./theme.json). Pull requests welcome — see [Contributing](./CONTRIBUTING.md).
237
+
238
+ </details>
239
+
240
+ <details>
241
+ <summary><strong>Where do I report bugs in the CMS or CLI?</strong></summary>
242
+
243
+ Theme issues → [this repo](https://github.com/fecommunity/reactpress-theme-starter/issues). Core ReactPress (API, admin, CLI) → [fecommunity/reactpress](https://github.com/fecommunity/reactpress/issues).
244
+
245
+ </details>
246
+
247
+ ---
248
+
249
+ ## Community
250
+
251
+ | Channel | Link |
252
+ | :-------------- | :---------------------------------------------------------------------------------------- |
253
+ | Q&A & showcase | [GitHub Discussions](https://github.com/fecommunity/reactpress-theme-starter/discussions) |
254
+ | Bug reports | [Issues](https://github.com/fecommunity/reactpress-theme-starter/issues) |
255
+ | ReactPress core | [github.com/fecommunity/reactpress](https://github.com/fecommunity/reactpress) |
256
+ | Documentation | [reactpress.surge.sh](https://reactpress.surge.sh/) |
257
+
258
+ ---
259
+
260
+ ## Contributing
261
+
262
+ | | |
263
+ | :------------------------------------------ | :-------------------------------- |
264
+ | [Contributing](./CONTRIBUTING.md) | Setup, conventions, pull requests |
265
+ | [Contributing (中文)](./CONTRIBUTING_zh.md) | 中文贡献指南 |
266
+ | [Code of Conduct](./CODE_OF_CONDUCT.md) | Community standards |
267
+ | [Security](./SECURITY.md) | Report vulnerabilities |
268
+
269
+ ---
270
+
271
+ ## Links
272
+
273
+ | | |
274
+ | :---------------- | :----------------------------------------------------------------------------------------------- |
275
+ | ReactPress docs | [reactpress.surge.sh](https://reactpress.surge.sh/) |
276
+ | ReactPress source | [github.com/fecommunity/reactpress](https://github.com/fecommunity/reactpress) |
277
+ | Toolkit | [@fecommunity/reactpress-toolkit](https://www.npmjs.com/package/@fecommunity/reactpress-toolkit) |
278
+ | Theme schema | [theme.manifest.schema.json](./theme.manifest.schema.json) |
279
+
280
+ <br />
281
+
282
+ <div align="center">
283
+
284
+ [MIT License](./LICENSE) · © ReactPress / FECommunity
285
+
286
+ </div>
@@ -0,0 +1,69 @@
1
+ import { isMockApiEnabled } from '@/lib/mock-api/config'
2
+ import { corsPreflightResponse, withCors } from '@/lib/mock-api/cors'
3
+ import { matchMockRoute } from '@/lib/mock-api/match-route'
4
+ import { proxyToUpstreamApi } from '@/lib/mock-api/proxy'
5
+
6
+ export const dynamic = 'force-dynamic'
7
+
8
+ type RouteContext = {
9
+ params: Promise<{ path?: string[] }>
10
+ }
11
+
12
+ async function readJsonBody(request: Request): Promise<unknown> {
13
+ if (request.method === 'GET' || request.method === 'HEAD') return undefined
14
+ try {
15
+ const text = await request.text()
16
+ if (!text.trim()) return undefined
17
+ return JSON.parse(text) as unknown
18
+ } catch {
19
+ return undefined
20
+ }
21
+ }
22
+
23
+ async function handleRequest(request: Request, context: RouteContext): Promise<Response> {
24
+ if (request.method === 'OPTIONS') {
25
+ return corsPreflightResponse(request)
26
+ }
27
+
28
+ const { path = [] } = await context.params
29
+ const keyword = new URL(request.url).searchParams.get('keyword')?.trim() ?? ''
30
+ const body = await readJsonBody(request)
31
+
32
+ let response: Response
33
+ if (isMockApiEnabled()) {
34
+ const mockResponse = matchMockRoute(request.method, path, { keyword, body })
35
+ response = mockResponse ?? Response.json({ success: true, data: null })
36
+ } else {
37
+ response = await proxyToUpstreamApi(request, path)
38
+ }
39
+
40
+ return withCors(request, response)
41
+ }
42
+
43
+ export function GET(request: Request, context: RouteContext) {
44
+ return handleRequest(request, context)
45
+ }
46
+
47
+ export function POST(request: Request, context: RouteContext) {
48
+ return handleRequest(request, context)
49
+ }
50
+
51
+ export function PUT(request: Request, context: RouteContext) {
52
+ return handleRequest(request, context)
53
+ }
54
+
55
+ export function PATCH(request: Request, context: RouteContext) {
56
+ return handleRequest(request, context)
57
+ }
58
+
59
+ export function DELETE(request: Request, context: RouteContext) {
60
+ return handleRequest(request, context)
61
+ }
62
+
63
+ export function HEAD(request: Request, context: RouteContext) {
64
+ return handleRequest(request, context)
65
+ }
66
+
67
+ export function OPTIONS(request: Request, context: RouteContext) {
68
+ return handleRequest(request, context)
69
+ }
@@ -0,0 +1,28 @@
1
+ import ArchivesClient from '@/components/views/ArchivesClient'
2
+ import { buildLocalizedListPageMetadata } from '@/lib/reactpress/siteMetadata'
3
+ import {
4
+ fetchArchivesPageProps,
5
+ themeApi,
6
+ withApiRetry,
7
+ type ArchiveTree,
8
+ } from '@fecommunity/reactpress-toolkit/theme/server'
9
+ import type { Metadata } from 'next'
10
+
11
+ export const revalidate = 60
12
+
13
+ export async function generateMetadata(): Promise<Metadata> {
14
+ return buildLocalizedListPageMetadata('archives', undefined, { path: '/archives' })
15
+ }
16
+
17
+ export default async function ArchivesPage() {
18
+ let articles: ArchiveTree = {}
19
+
20
+ try {
21
+ const data = await withApiRetry(() => fetchArchivesPageProps(themeApi))
22
+ articles = data.articles
23
+ } catch (error) {
24
+ console.error('[reactpress-theme-starter] archives fetch failed', error)
25
+ }
26
+
27
+ return <ArchivesClient articles={articles} />
28
+ }
@@ -0,0 +1,92 @@
1
+ import ArticleViewClient from '@/components/article/ArticleViewClient'
2
+ import {
3
+ buildArticleJsonLd,
4
+ buildArticleMetadata,
5
+ parseSiteSeoContext,
6
+ } from '@/lib/reactpress/contentSeo'
7
+ import { generateArticleStaticParams } from '@/lib/reactpress/staticParams'
8
+ import {
9
+ fetchArticleDetailProps,
10
+ fetchSiteMeta,
11
+ resolveImageUrl,
12
+ themeApi,
13
+ unwrapSetting,
14
+ withApiRetry,
15
+ } from '@fecommunity/reactpress-toolkit/theme/server'
16
+ import type { Metadata } from 'next'
17
+ import { notFound } from 'next/navigation'
18
+
19
+ export const revalidate = 60
20
+
21
+ export async function generateStaticParams() {
22
+ return generateArticleStaticParams()
23
+ }
24
+
25
+ interface PageProps {
26
+ params: Promise<{ id: string }>
27
+ }
28
+
29
+ function resolveAbsoluteImageUrl(cover: string | undefined, siteUrl?: string) {
30
+ if (!cover) return undefined
31
+ const imageUrl = resolveImageUrl(cover, 'large')
32
+ if (!siteUrl) return imageUrl
33
+ try {
34
+ return new URL(imageUrl, siteUrl).href
35
+ } catch {
36
+ return imageUrl
37
+ }
38
+ }
39
+
40
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
41
+ const { id } = await params
42
+
43
+ try {
44
+ const [{ article }, siteMeta, settingRow] = await Promise.all([
45
+ withApiRetry(() => fetchArticleDetailProps(themeApi, id)),
46
+ withApiRetry(() => fetchSiteMeta(themeApi)),
47
+ withApiRetry(() => themeApi.setting.findAll()).then(unwrapSetting),
48
+ ])
49
+
50
+ if (!article?.id || article.status !== 'publish') {
51
+ return {}
52
+ }
53
+
54
+ const site = parseSiteSeoContext(settingRow, siteMeta)
55
+ const imageUrl = resolveAbsoluteImageUrl(article.cover, site.siteUrl)
56
+ return buildArticleMetadata(article, site, imageUrl)
57
+ } catch (error) {
58
+ console.error('[reactpress-theme-starter] article metadata fetch failed', error)
59
+ return {}
60
+ }
61
+ }
62
+
63
+ export default async function ArticlePage({ params }: PageProps) {
64
+ const { id } = await params
65
+
66
+ try {
67
+ const [{ article }, siteMeta] = await Promise.all([
68
+ withApiRetry(() => fetchArticleDetailProps(themeApi, id)),
69
+ withApiRetry(() => fetchSiteMeta(themeApi)),
70
+ ])
71
+
72
+ if (!article?.id || article.status !== 'publish') {
73
+ notFound()
74
+ }
75
+
76
+ const coverUrl = resolveAbsoluteImageUrl(article.cover, siteMeta.siteUrl)
77
+ const jsonLd = buildArticleJsonLd(article, siteMeta.siteUrl, coverUrl)
78
+
79
+ return (
80
+ <>
81
+ <script
82
+ type="application/ld+json"
83
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
84
+ />
85
+ <ArticleViewClient article={article} />
86
+ </>
87
+ )
88
+ } catch (error) {
89
+ console.error('[reactpress-theme-starter] article page fetch failed', error)
90
+ notFound()
91
+ }
92
+ }
@@ -0,0 +1,60 @@
1
+ import CategoryClient from '@/components/views/CategoryClient'
2
+ import { buildLocalizedListPageMetadata } from '@/lib/reactpress/siteMetadata'
3
+ import { generateCategoryStaticParams } from '@/lib/reactpress/staticParams'
4
+ import {
5
+ fetchCategoryArchivePageProps,
6
+ themeApi,
7
+ withApiRetry,
8
+ } from '@fecommunity/reactpress-toolkit/theme/server'
9
+ import type { Metadata } from 'next'
10
+ import { notFound } from 'next/navigation'
11
+
12
+ export const revalidate = 60
13
+
14
+ export async function generateStaticParams() {
15
+ return generateCategoryStaticParams()
16
+ }
17
+
18
+ interface PageProps {
19
+ params: Promise<{ category: string }>
20
+ }
21
+
22
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
23
+ const { category } = await params
24
+ const categoryValue = decodeURIComponent(category)
25
+
26
+ try {
27
+ const data = await withApiRetry(() => fetchCategoryArchivePageProps(themeApi, categoryValue))
28
+ const label = data.category?.label || categoryValue
29
+ return buildLocalizedListPageMetadata(
30
+ 'pageTitleCategory',
31
+ { label },
32
+ {
33
+ path: `/category/${encodeURIComponent(categoryValue)}`,
34
+ }
35
+ )
36
+ } catch {
37
+ return buildLocalizedListPageMetadata(
38
+ 'pageTitleCategory',
39
+ { label: categoryValue },
40
+ {
41
+ path: `/category/${encodeURIComponent(categoryValue)}`,
42
+ }
43
+ )
44
+ }
45
+ }
46
+
47
+ export default async function CategoryArchivePage({ params }: PageProps) {
48
+ const { category } = await params
49
+ const categoryValue = decodeURIComponent(category)
50
+
51
+ try {
52
+ const data = await withApiRetry(() => fetchCategoryArchivePageProps(themeApi, categoryValue))
53
+ return (
54
+ <CategoryClient initialArticles={data.articles} total={data.total} category={data.category} />
55
+ )
56
+ } catch (error) {
57
+ console.error('[reactpress-theme-starter] category archive fetch failed', error)
58
+ notFound()
59
+ }
60
+ }