next-posts 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 yd-tw
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-zh.md ADDED
@@ -0,0 +1,334 @@
1
+ # next-posts
2
+
3
+ > 載入文章並將 YAML 解析到 Next.js 中!
4
+
5
+ 一個輕量、零依賴的 TypeScript 函式庫,用於在 Next.js 中建立靜態部落格等文章頁面。它會解析包含 YAML frontmatter 的 Markdown 檔案,並提供簡潔的工具函式,方便搭配 SSG(Static Site Generation)使用。
6
+
7
+ ---
8
+
9
+ ## 功能特色
10
+
11
+ - 零執行期依賴 —— 僅使用 Node.js 內建模組
12
+ - 完整 TypeScript 支援(支援泛型 metadata)
13
+ - 支援任意目錄結構
14
+ - 同時支援 `slug` 與 `slug.md` 輸入格式
15
+ - 相容 Next.js App Router 與 Pages Router
16
+
17
+ ---
18
+
19
+ ## 安裝
20
+
21
+ 使用 npm 或你喜歡的套件管理工具。
22
+
23
+ ```bash
24
+ npm install next-posts
25
+ ```
26
+
27
+ ---
28
+
29
+ ## 快速開始
30
+
31
+ 在專案根目錄建立 `posts/` 資料夾,並放入 Markdown 檔案:
32
+
33
+ ```
34
+ my-next-app/
35
+ └── posts/
36
+ ├── hello-world.md
37
+ └── getting-started.md
38
+ ```
39
+
40
+ 每個檔案應包含 YAML frontmatter 區塊與文章內容:
41
+
42
+ ```markdown
43
+ ---
44
+ title: Hello World
45
+ date: 2024-01-01
46
+ tags:
47
+ - blog
48
+ - intro
49
+ ---
50
+
51
+ Welcome to my blog!
52
+ ```
53
+
54
+ 接著在 Next.js 中使用:
55
+
56
+ ```ts
57
+ import { getAllPosts, getPostBySlug, getAllPostParams } from "next-posts";
58
+
59
+ interface PostMeta {
60
+ title: string;
61
+ date: string;
62
+ tags?: string[];
63
+ }
64
+
65
+ // 取得所有文章
66
+ const posts = getAllPosts<PostMeta>();
67
+
68
+ // 取得單篇文章
69
+ const post = getPostBySlug<PostMeta>("hello-world");
70
+
71
+ // 產生靜態路徑(Next.js)
72
+ const paths = getAllPostParams();
73
+ ```
74
+
75
+ ### 模板
76
+
77
+ 你也可以從模板開始:
78
+
79
+ - [next-profile-template](https://github.com/yd-tw/next-profile-template)
80
+ - [kuang-ti-web](https://github.com/yd-tw/kuang-ti-web)
81
+
82
+ ---
83
+
84
+ ## API
85
+
86
+ ### `getAllPosts<T>(directory?)`
87
+
88
+ 回傳指定目錄中的所有文章,包含解析後的 frontmatter 與內容。
89
+
90
+ ```ts
91
+ const posts = getAllPosts<PostMeta>();
92
+ // [
93
+ // { slug: 'hello-world', metadata: { title: '...' }, content: '...' },
94
+ // ...
95
+ // ]
96
+ ```
97
+
98
+ | 參數 | 型別 | 預設值 | 說明 |
99
+ | ----------- | -------- | ---------- | ----------------------------- |
100
+ | `directory` | `string` | `"posts/"` | 相對於 `process.cwd()` 的路徑 |
101
+
102
+ **回傳型別:**
103
+ `Array<{ slug: string; metadata: T; content: string }>`
104
+
105
+ ---
106
+
107
+ ### `getPostBySlug<T>(slug, directory?)`
108
+
109
+ 依據 slug 取得單篇文章。支援帶或不帶 `.md` 副檔名。
110
+
111
+ ```ts
112
+ const post = getPostBySlug<PostMeta>("hello-world");
113
+ // {
114
+ // slug: 'hello-world',
115
+ // metadata: { title: 'Hello World', ... },
116
+ // content: '...'
117
+ // }
118
+ ```
119
+
120
+ | 參數 | 型別 | 預設值 | 說明 |
121
+ | ----------- | -------- | ---------- | ----------------------------- |
122
+ | `slug` | `string` | — | 檔名(可含或不含 `.md`) |
123
+ | `directory` | `string` | `"posts/"` | 相對於 `process.cwd()` 的路徑 |
124
+
125
+ **回傳型別:**
126
+ `{ slug: string; metadata: T; content: string }`
127
+
128
+ ---
129
+
130
+ ### `getAllPostSlugs(directory?)`
131
+
132
+ 回傳指定目錄中的原始檔名(包含 `.md` 副檔名)。
133
+
134
+ ```ts
135
+ const slugs = getAllPostSlugs();
136
+ // ['hello-world.md', 'getting-started.md']
137
+ ```
138
+
139
+ | 參數 | 型別 | 預設值 | 說明 |
140
+ | ----------- | -------- | ---------- | ----------------------------- |
141
+ | `directory` | `string` | `"posts/"` | 相對於 `process.cwd()` 的路徑 |
142
+
143
+ **回傳型別:**
144
+ `string[]`
145
+
146
+ ---
147
+
148
+ ### `getAllPostParams(directory?)`
149
+
150
+ 回傳適用於 Next.js `generateStaticParams` 或 `getStaticPaths` 的 slug 參數物件,會自動移除 `.md` 副檔名。
151
+
152
+ ```ts
153
+ const params = getAllPostParams();
154
+ // [{ slug: 'hello-world' }, { slug: 'getting-started' }]
155
+ ```
156
+
157
+ | 參數 | 型別 | 預設值 | 說明 |
158
+ | ----------- | -------- | ---------- | ----------------------------- |
159
+ | `directory` | `string` | `"posts/"` | 相對於 `process.cwd()` 的路徑 |
160
+
161
+ **回傳型別:**
162
+ `Array<{ slug: string }>`
163
+
164
+ ---
165
+
166
+ ### `parseFrontmatter(input)`
167
+
168
+ 解析原始 Markdown 字串並提取 YAML frontmatter。適用於非從檔案系統讀取的 Markdown,或需要較底層控制時使用。
169
+
170
+ ```ts
171
+ import { parseFrontmatter } from "next-posts";
172
+
173
+ const raw = `---
174
+ title: Hello World
175
+ date: 2024-01-01
176
+ tags:
177
+ - blog
178
+ ---
179
+
180
+ Welcome to my blog!`;
181
+
182
+ const { data, content } = parseFrontmatter(raw);
183
+ ```
184
+
185
+ - `data`:解析後的 YAML frontmatter 物件,若無有效 frontmatter 則為 `{}`。
186
+ - `content`:移除 frontmatter 後的 Markdown 內容。
187
+
188
+ | 參數 | 型別 | 說明 |
189
+ | ------- | -------- | -------------------------------- |
190
+ | `input` | `string` | 含有 frontmatter 的原始 Markdown |
191
+
192
+ **回傳型別:**
193
+ `{ data: Record<string, unknown>; content: string }`
194
+
195
+ ---
196
+
197
+ ## 搭配 Next.js 使用
198
+
199
+ ### App Router
200
+
201
+ ```tsx
202
+ // app/blog/[slug]/page.tsx
203
+ import { getAllPostParams, getPostBySlug } from "next-posts";
204
+
205
+ interface PostMeta {
206
+ title: string;
207
+ date: string;
208
+ }
209
+
210
+ export function generateStaticParams() {
211
+ return getAllPostParams();
212
+ }
213
+
214
+ export default async function Page({
215
+ params,
216
+ }: {
217
+ params: Promise<{ slug: string }>;
218
+ }) {
219
+ const { slug } = await params;
220
+ const { metadata, content } = getPostBySlug<PostMeta>(slug);
221
+
222
+ return (
223
+ <article>
224
+ <h1>{metadata.title}</h1>
225
+ <p>{metadata.date}</p>
226
+ {/* 推薦使用 ReactMarkdown,但你可以使用任何喜愛的 */}
227
+ <ReactMarkdown>{post.content}</ReactMarkdown>
228
+ </article>
229
+ );
230
+ }
231
+ ```
232
+
233
+ ---
234
+
235
+ ### Pages Router
236
+
237
+ ```ts
238
+ // pages/blog/[slug].tsx
239
+ import { GetStaticPaths, GetStaticProps } from "next";
240
+ import { getAllPostParams, getPostBySlug } from "next-posts";
241
+
242
+ interface PostMeta {
243
+ title: string;
244
+ date: string;
245
+ }
246
+
247
+ export const getStaticPaths: GetStaticPaths = () => ({
248
+ paths: getAllPostParams().map(({ slug }) => ({ params: { slug } })),
249
+ fallback: false,
250
+ });
251
+
252
+ export const getStaticProps: GetStaticProps = ({ params }) => {
253
+ const post = getPostBySlug<PostMeta>(params!.slug as string);
254
+ return { props: { post } };
255
+ };
256
+ ```
257
+
258
+ ---
259
+
260
+ ## 自訂文章目錄
261
+
262
+ 所有函式皆支援傳入自訂目錄:
263
+
264
+ ```ts
265
+ const posts = getAllPosts("content/articles/");
266
+ const post = getPostBySlug("my-article", "content/articles/");
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 從 v0.x 版本遷移
272
+
273
+ 遷移過程很容易,因為大部分的 API 都保持兼容。
274
+
275
+ 1. 變更套件名稱:
276
+
277
+ `next-posts` 過去稱為 `next-staticblog` ,你可以直接替換套件名稱並指向最新版本。舊的套件將棄用並不再進行維護。
278
+
279
+ 2. 明確的型別:
280
+
281
+ 在 `v0.x` 版本中使用 `any` 來定義 `metadata`,
282
+ 在 `v1.x` 版本中改為使用 `unknown` 來提升安全性。
283
+ 請閱讀文檔瞭解如何傳入泛型,擁有安全的型別。
284
+
285
+ 除此之外套件移除了 `gray-matter` 並改用 `@std/yaml` 進行解析。雖然變更通過了所有的測試,但仍然可能在部分非標準用法上有細微差異。
286
+
287
+ ---
288
+
289
+ ## 常見問題
290
+
291
+ ### no such file or directory
292
+
293
+ 我們推薦你使用 SSG 建置,因為這樣才能確保最高效率。因此若你在動態路由中使用 `next-posts` 將有可能導致錯誤。
294
+ 幸運的是通常你只需要使用 `getAllPostParams()` 並結合 `generateStaticParams()` 就能使用 `SSG`。
295
+
296
+ ```ts
297
+ Error: ENOENT: no such file or directory, scandir '/var/task/posts/news/zh'
298
+ ```
299
+
300
+ 但若你堅持使用動態路由,則需要在 next.config.ts 添加設置以確保文章能夠被包含。
301
+
302
+ ```ts
303
+ // next.config.ts
304
+ outputFileTracingIncludes: {
305
+ "/**": ["./posts/**/*.{md,mdx}"],
306
+ },
307
+ ```
308
+
309
+ ---
310
+
311
+ ## 貢獻
312
+
313
+ 你可以透過回報問題或提交新功能完善這個套件!
314
+
315
+ ```bash
316
+ # 安裝依賴
317
+ npm install
318
+
319
+ # 建置
320
+ npm run build
321
+
322
+ # 執行測試
323
+ npm test
324
+
325
+ # 格式化程式碼
326
+ npm run format
327
+ ```
328
+
329
+ ---
330
+
331
+ ## 授權
332
+
333
+ MIT © yd-tw
334
+ [https://github.com/yd-tw/next-posts](https://github.com/yd-tw/next-posts)
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # next-posts
2
+
3
+ > Load posts and parse YAML into Next.js!
4
+
5
+ A lightweight, zero-dependency TypeScript library for building static blog posts and content pages in Next.js. It parses Markdown files with YAML frontmatter and provides simple utility functions designed for use with SSG (Static Site Generation).
6
+
7
+ [中文](/README-zh.md)
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - Zero runtime dependencies — only uses built-in Node.js modules
14
+ - Full TypeScript support (generic metadata support)
15
+ - Supports arbitrary directory structures
16
+ - Accepts both `slug` and `slug.md` formats
17
+ - Compatible with Next.js App Router and Pages Router
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ Install using npm or your preferred package manager:
24
+
25
+ ```bash
26
+ npm install next-posts
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ Create a `posts/` directory in your project root and add Markdown files:
34
+
35
+ ```
36
+ my-next-app/
37
+ └── posts/
38
+ ├── hello-world.md
39
+ └── getting-started.md
40
+ ```
41
+
42
+ Each file should contain a YAML frontmatter block and content:
43
+
44
+ ```markdown
45
+ ---
46
+ title: Hello World
47
+ date: 2024-01-01
48
+ tags:
49
+ - blog
50
+ - intro
51
+ ---
52
+
53
+ Welcome to my blog!
54
+ ```
55
+
56
+ Then use it in your Next.js project:
57
+
58
+ ```ts
59
+ import { getAllPosts, getPostBySlug, getAllPostParams } from "next-posts";
60
+
61
+ interface PostMeta {
62
+ title: string;
63
+ date: string;
64
+ tags?: string[];
65
+ }
66
+
67
+ // Get all posts
68
+ const posts = getAllPosts<PostMeta>();
69
+
70
+ // Get a single post
71
+ const post = getPostBySlug<PostMeta>("hello-world");
72
+
73
+ // Generate static paths (Next.js)
74
+ const paths = getAllPostParams();
75
+ ```
76
+
77
+ ### Template
78
+
79
+ You can also start with a template:
80
+
81
+ - [next-profile-template](https://github.com/yd-tw/next-profile-template)
82
+ - [kuang-ti-web](https://github.com/yd-tw/kuang-ti-web)
83
+
84
+ ---
85
+
86
+ ## API
87
+
88
+ ### `getAllPosts<T>(directory?)`
89
+
90
+ Returns all posts from the specified directory, including parsed frontmatter and content.
91
+
92
+ ```ts
93
+ const posts = getAllPosts<PostMeta>();
94
+ // [
95
+ // { slug: 'hello-world', metadata: { title: '...' }, content: '...' },
96
+ // ...
97
+ // ]
98
+ ```
99
+
100
+ | Parameter | Type | Default | Description |
101
+ | ----------- | -------- | ---------- | -------------------------------- |
102
+ | `directory` | `string` | `"posts/"` | Path relative to `process.cwd()` |
103
+
104
+ **Return type:**
105
+ `Array<{ slug: string; metadata: T; content: string }>`
106
+
107
+ ---
108
+
109
+ ### `getPostBySlug<T>(slug, directory?)`
110
+
111
+ Returns a single post by slug. Supports both with and without the `.md` extension.
112
+
113
+ ```ts
114
+ const post = getPostBySlug<PostMeta>("hello-world");
115
+ // {
116
+ // slug: 'hello-world',
117
+ // metadata: { title: 'Hello World', ... },
118
+ // content: '...'
119
+ // }
120
+ ```
121
+
122
+ | Parameter | Type | Default | Description |
123
+ | ----------- | -------- | ---------- | -------------------------------- |
124
+ | `slug` | `string` | — | Filename (with or without `.md`) |
125
+ | `directory` | `string` | `"posts/"` | Path relative to `process.cwd()` |
126
+
127
+ **Return type:**
128
+ `{ slug: string; metadata: T; content: string }`
129
+
130
+ ---
131
+
132
+ ### `getAllPostSlugs(directory?)`
133
+
134
+ Returns all raw filenames (including the `.md` extension) from the specified directory.
135
+
136
+ ```ts
137
+ const slugs = getAllPostSlugs();
138
+ // ['hello-world.md', 'getting-started.md']
139
+ ```
140
+
141
+ | Parameter | Type | Default | Description |
142
+ | ----------- | -------- | ---------- | -------------------------------- |
143
+ | `directory` | `string` | `"posts/"` | Path relative to `process.cwd()` |
144
+
145
+ **Return type:**
146
+ `string[]`
147
+
148
+ ---
149
+
150
+ ### `getAllPostParams(directory?)`
151
+
152
+ Returns slug parameter objects suitable for Next.js `generateStaticParams` or `getStaticPaths`. Automatically removes the `.md` extension.
153
+
154
+ ```ts
155
+ const params = getAllPostParams();
156
+ // [{ slug: 'hello-world' }, { slug: 'getting-started' }]
157
+ ```
158
+
159
+ | Parameter | Type | Default | Description |
160
+ | ----------- | -------- | ---------- | -------------------------------- |
161
+ | `directory` | `string` | `"posts/"` | Path relative to `process.cwd()` |
162
+
163
+ **Return type:**
164
+ `Array<{ slug: string }>`
165
+
166
+ ---
167
+
168
+ ### `parseFrontmatter(input)`
169
+
170
+ Parses a raw Markdown string and extracts YAML frontmatter. Useful when Markdown is not loaded from the filesystem or when lower-level control is needed.
171
+
172
+ ```ts
173
+ import { parseFrontmatter } from "next-posts";
174
+
175
+ const raw = `---
176
+ title: Hello World
177
+ date: 2024-01-01
178
+ tags:
179
+ - blog
180
+ ---
181
+
182
+ Welcome to my blog!`;
183
+
184
+ const { data, content } = parseFrontmatter(raw);
185
+ ```
186
+
187
+ - `data`: Parsed YAML frontmatter object. Returns `{}` if no valid frontmatter exists.
188
+ - `content`: Markdown content with the frontmatter removed.
189
+
190
+ | Parameter | Type | Description |
191
+ | --------- | -------- | ------------------------------------------ |
192
+ | `input` | `string` | Raw Markdown string containing frontmatter |
193
+
194
+ **Return type:**
195
+ `{ data: Record<string, unknown>; content: string }`
196
+
197
+ ---
198
+
199
+ ## Using with Next.js
200
+
201
+ ### App Router
202
+
203
+ ```tsx
204
+ // app/blog/[slug]/page.tsx
205
+ import { getAllPostParams, getPostBySlug } from "next-posts";
206
+
207
+ interface PostMeta {
208
+ title: string;
209
+ date: string;
210
+ }
211
+
212
+ export function generateStaticParams() {
213
+ return getAllPostParams();
214
+ }
215
+
216
+ export default async function Page({
217
+ params,
218
+ }: {
219
+ params: Promise<{ slug: string }>;
220
+ }) {
221
+ const { slug } = await params;
222
+ const { metadata, content } = getPostBySlug<PostMeta>(slug);
223
+
224
+ return (
225
+ <article>
226
+ <h1>{metadata.title}</h1>
227
+ <p>{metadata.date}</p>
228
+ {/* ReactMarkdown is recommended, but you can use any renderer you prefer */}
229
+ <ReactMarkdown>{content}</ReactMarkdown>
230
+ </article>
231
+ );
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Pages Router
238
+
239
+ ```ts
240
+ // pages/blog/[slug].tsx
241
+ import { GetStaticPaths, GetStaticProps } from "next";
242
+ import { getAllPostParams, getPostBySlug } from "next-posts";
243
+
244
+ interface PostMeta {
245
+ title: string;
246
+ date: string;
247
+ }
248
+
249
+ export const getStaticPaths: GetStaticPaths = () => ({
250
+ paths: getAllPostParams().map(({ slug }) => ({ params: { slug } })),
251
+ fallback: false,
252
+ });
253
+
254
+ export const getStaticProps: GetStaticProps = ({ params }) => {
255
+ const post = getPostBySlug<PostMeta>(params!.slug as string);
256
+ return { props: { post } };
257
+ };
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Custom Post Directory
263
+
264
+ All functions accept a custom directory:
265
+
266
+ ```ts
267
+ const posts = getAllPosts("content/articles/");
268
+ const post = getPostBySlug("my-article", "content/articles/");
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Migrating from v0.x
274
+
275
+ Migration is straightforward since most APIs remain compatible.
276
+
277
+ 1. Package rename:
278
+
279
+ `next-posts` was previously called `next-staticblog`. Simply replace the package name and install the latest version. The old package is deprecated and no longer maintained.
280
+
281
+ 2. Explicit typing:
282
+
283
+ In `v0.x`, `metadata` was typed as `any`.
284
+ In `v1.x`, it is typed as `unknown` to improve type safety.
285
+ Refer to the documentation on how to pass generics for safe typing.
286
+
287
+ Additionally, the package removed `gray-matter` and now uses `@std/yaml` for parsing. While all tests pass, there may be minor differences in some non-standard use cases.
288
+
289
+ ---
290
+
291
+ ## FAQ
292
+
293
+ ### no such file or directory
294
+
295
+ SSG is strongly recommended for maximum efficiency. Using `next-posts` inside dynamic routes may cause errors.
296
+
297
+ Typically, you only need to combine `getAllPostParams()` with `generateStaticParams()` to enable SSG.
298
+
299
+ ```ts
300
+ Error: ENOENT: no such file or directory, scandir '/var/task/posts/news/zh'
301
+ ```
302
+
303
+ If you insist on using dynamic routes, add the following configuration to `next.config.ts` to ensure Markdown files are included:
304
+
305
+ ```ts
306
+ // next.config.ts
307
+ outputFileTracingIncludes: {
308
+ "/**": ["./posts/**/*.{md,mdx}"],
309
+ },
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Contributing
315
+
316
+ You can improve this package by reporting issues or submitting new features!
317
+
318
+ ```bash
319
+ # Install dependencies
320
+ npm install
321
+
322
+ # Build
323
+ npm run build
324
+
325
+ # Run tests
326
+ npm test
327
+
328
+ # Format code
329
+ npm run format
330
+ ```
331
+
332
+ ---
333
+
334
+ ## License
335
+
336
+ MIT © yd-tw
337
+ [https://github.com/yd-tw/next-posts](https://github.com/yd-tw/next-posts)
@@ -0,0 +1,18 @@
1
+ export declare function parseFrontmatter(input: string): {
2
+ data: Record<string, unknown>;
3
+ content: string;
4
+ };
5
+ export declare function getAllPostSlugs(directory?: string): string[];
6
+ export declare function getAllPosts<T extends object = Record<string, unknown>>(directory?: string): {
7
+ slug: string;
8
+ metadata: T;
9
+ content: string;
10
+ }[];
11
+ export declare function getAllPostParams(directory?: string): {
12
+ slug: string;
13
+ }[];
14
+ export declare function getPostBySlug<T extends object = Record<string, unknown>>(slug: string, directory?: string): {
15
+ slug: string;
16
+ metadata: T;
17
+ content: string;
18
+ };