@earlyseo/blog 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -20,13 +20,14 @@ Articles are authored in the EarlySEO dashboard and rendered on your site. No da
20
20
 
21
21
  ```bash
22
22
  npm install @earlyseo/blog
23
- npx earlyseo-blog
23
+ npx @earlyseo/blog init
24
24
  ```
25
25
 
26
26
  The CLI will detect your Next.js project, ask for your site ID, and generate:
27
27
  - `app/blog/page.tsx` — Paginated blog listing
28
28
  - `app/blog/[slug]/page.tsx` — Article detail with full SEO metadata
29
- - `.env.local` — `EARLYSEO_SITE_ID` added automatically
29
+ - `app/sitemap.ts` — Blog URLs added to sitemap automatically
30
+ - `.env.local` — `EARLYSEO_SITE_ID` and `NEXT_PUBLIC_BASE_URL` added automatically
30
31
 
31
32
  That's it — run `npm run dev` and visit `/blog`.
32
33
 
@@ -43,6 +44,7 @@ npm install @earlyseo/blog
43
44
  ```env
44
45
  # .env.local
45
46
  EARLYSEO_SITE_ID=your-site-id
47
+ NEXT_PUBLIC_BASE_URL=https://example.com
46
48
  ```
47
49
 
48
50
  ### 3. Add the blog listing page
@@ -77,6 +79,39 @@ export const generateMetadata = createBlogPostMetadata({ siteId, siteName: "My S
77
79
 
78
80
  That's it! Articles published from EarlySEO automatically appear on your blog.
79
81
 
82
+ ### 5. Add sitemap entries
83
+
84
+ If you already have an existing `app/sitemap.ts`, merge blog entries into it:
85
+
86
+ ```tsx
87
+ import { getBlogSitemapEntries } from "@earlyseo/blog/next";
88
+ import type { MetadataRoute } from "next";
89
+
90
+ const siteId = process.env.EARLYSEO_SITE_ID!;
91
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "https://example.com";
92
+
93
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
94
+ const blogEntries = await getBlogSitemapEntries({ siteId, baseUrl });
95
+
96
+ return [
97
+ { url: baseUrl, lastModified: new Date() },
98
+ ...blogEntries,
99
+ ];
100
+ }
101
+ ```
102
+
103
+ Or create a dedicated blog sitemap:
104
+
105
+ ```tsx
106
+ // app/blog/sitemap.ts
107
+ import { createBlogSitemap } from "@earlyseo/blog/next";
108
+
109
+ const siteId = process.env.EARLYSEO_SITE_ID!;
110
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "https://example.com";
111
+
112
+ export default createBlogSitemap({ siteId, baseUrl });
113
+ ```
114
+
80
115
  ---
81
116
 
82
117
  ## How It Works
@@ -280,6 +315,15 @@ interface Article {
280
315
  canonicalUrl?: string;
281
316
  version: number;
282
317
  }
318
+
319
+ interface BlogSitemapOptions {
320
+ siteId: string;
321
+ baseUrl: string;
322
+ basePath?: string;
323
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
324
+ priority?: number;
325
+ fetchInit?: RequestInit;
326
+ }
283
327
  ```
284
328
 
285
329
  ### Imports
@@ -288,7 +332,7 @@ interface Article {
288
332
  |-------------|----------|
289
333
  | `@earlyseo/blog` | `EarlySeoClient`, core types, CSS constants |
290
334
  | `@earlyseo/blog/react` | Provider, hooks, components |
291
- | `@earlyseo/blog/next` | Next.js page creators, metadata helpers |
335
+ | `@earlyseo/blog/next` | Next.js page creators, metadata helpers, sitemap helpers |
292
336
  | `@earlyseo/blog/css` | `ARTICLE_CSS`, `BLOG_CSS` |
293
337
 
294
338
  ---
@@ -1,5 +1,6 @@
1
1
  export { createBlogListPage, type BlogListPageOptions } from "./blog-list-page";
2
2
  export { createBlogPostPage, createBlogPostMetadata, createBlogListMetadata, type BlogPostPageOptions, } from "./blog-post-page";
3
3
  export { BackLink } from "./back-link";
4
+ export { createBlogSitemap, getBlogSitemapEntries, type BlogSitemapOptions, type SitemapEntry, } from "./sitemap";
4
5
  export { EarlySeoClient } from "../client";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -6,6 +6,8 @@ export { createBlogListPage } from "./blog-list-page";
6
6
  export { createBlogPostPage, createBlogPostMetadata, createBlogListMetadata, } from "./blog-post-page";
7
7
  // Client components
8
8
  export { BackLink } from "./back-link";
9
+ // Sitemap
10
+ export { createBlogSitemap, getBlogSitemapEntries, } from "./sitemap";
9
11
  // Re-export client for convenience
10
12
  export { EarlySeoClient } from "../client";
11
13
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mCAAmC;AACnC,gFAAgF;AAEhF,yBAAyB;AACzB,OAAO,EAAE,kBAAkB,EAA4B,MAAM,kBAAkB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,GAEvB,MAAM,kBAAkB,CAAC;AAE1B,oBAAoB;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,mCAAmC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mCAAmC;AACnC,gFAAgF;AAEhF,yBAAyB;AACzB,OAAO,EAAE,kBAAkB,EAA4B,MAAM,kBAAkB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,GAEvB,MAAM,kBAAkB,CAAC;AAE1B,oBAAoB;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,UAAU;AACV,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GAGtB,MAAM,WAAW,CAAC;AAEnB,mCAAmC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { EarlySeoConfig } from "../types";
2
+ export interface BlogSitemapOptions extends EarlySeoConfig {
3
+ /** Your site's public base URL (e.g. "https://example.com") */
4
+ baseUrl: string;
5
+ /** Blog base path (default: "/blog") */
6
+ basePath?: string;
7
+ /** Change frequency hint for sitemap (default: "weekly") */
8
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
9
+ /** Priority hint for blog articles (default: 0.7) */
10
+ priority?: number;
11
+ /** Additional fetch options (e.g. `{ next: { revalidate: 60 } }`) */
12
+ fetchInit?: RequestInit;
13
+ }
14
+ export interface SitemapEntry {
15
+ url: string;
16
+ lastModified?: Date | string;
17
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
18
+ priority?: number;
19
+ }
20
+ /**
21
+ * Fetches all published blog articles and returns an array of sitemap entries.
22
+ * Use this when you want to merge blog entries into an existing sitemap.
23
+ */
24
+ export declare function getBlogSitemapEntries(options: BlogSitemapOptions): Promise<SitemapEntry[]>;
25
+ /**
26
+ * Creates a Next.js App Router sitemap function that returns blog article entries.
27
+ * Use as the default export of `app/blog/sitemap.ts` or merge into `app/sitemap.ts`.
28
+ */
29
+ export declare function createBlogSitemap(options: BlogSitemapOptions): () => Promise<SitemapEntry[]>;
30
+ //# sourceMappingURL=sitemap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/next/sitemap.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,eAAe,CAAC,EACZ,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;IACZ,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC7B,eAAe,CAAC,EACZ,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC,CA6CzB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,SAC1B,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzD"}
@@ -0,0 +1,76 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // @earlyseo/blog — Next.js Sitemap Helper
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Generates sitemap entries for all published blog articles.
5
+ // Designed for Next.js App Router `sitemap.ts` files.
6
+ //
7
+ // Usage:
8
+ // // app/sitemap.ts (or app/blog/sitemap.ts)
9
+ // import { createBlogSitemap } from "@earlyseo/blog/next";
10
+ //
11
+ // const siteId = process.env.EARLYSEO_SITE_ID!;
12
+ // const blogSitemap = createBlogSitemap({ siteId, baseUrl: "https://example.com" });
13
+ // export default blogSitemap;
14
+ //
15
+ // // Or merge with your own sitemap entries:
16
+ // import { getBlogSitemapEntries } from "@earlyseo/blog/next";
17
+ //
18
+ // export default async function sitemap() {
19
+ // const blogEntries = await getBlogSitemapEntries({
20
+ // siteId: process.env.EARLYSEO_SITE_ID!,
21
+ // baseUrl: "https://example.com",
22
+ // });
23
+ // return [
24
+ // { url: "https://example.com", lastModified: new Date() },
25
+ // ...blogEntries,
26
+ // ];
27
+ // }
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ import { EarlySeoClient } from "../client";
30
+ /**
31
+ * Fetches all published blog articles and returns an array of sitemap entries.
32
+ * Use this when you want to merge blog entries into an existing sitemap.
33
+ */
34
+ export async function getBlogSitemapEntries(options) {
35
+ const { siteId, cdnBaseUrl, baseUrl, basePath = "/blog", changeFrequency = "weekly", priority = 0.7, fetchInit, } = options;
36
+ const client = new EarlySeoClient({ siteId, cdnBaseUrl }, fetchInit);
37
+ const entries = [];
38
+ const manifest = await client.getManifest();
39
+ if (!manifest)
40
+ return entries;
41
+ const normalizedBase = baseUrl.replace(/\/+$/, "");
42
+ const normalizedPath = basePath.replace(/\/+$/, "");
43
+ // Add the blog list page itself
44
+ entries.push({
45
+ url: `${normalizedBase}${normalizedPath}`,
46
+ lastModified: manifest.updatedAt,
47
+ changeFrequency,
48
+ priority: priority - 0.1,
49
+ });
50
+ // Fetch all list pages to gather every article slug
51
+ const totalPages = manifest.totalPages;
52
+ for (let page = 1; page <= totalPages; page++) {
53
+ const listPage = await client.getListPage(page);
54
+ if (!listPage)
55
+ continue;
56
+ for (const article of listPage.articles) {
57
+ entries.push({
58
+ url: `${normalizedBase}${normalizedPath}/${article.slug}`,
59
+ lastModified: article.createdAt,
60
+ changeFrequency,
61
+ priority,
62
+ });
63
+ }
64
+ }
65
+ return entries;
66
+ }
67
+ /**
68
+ * Creates a Next.js App Router sitemap function that returns blog article entries.
69
+ * Use as the default export of `app/blog/sitemap.ts` or merge into `app/sitemap.ts`.
70
+ */
71
+ export function createBlogSitemap(options) {
72
+ return async function sitemap() {
73
+ return getBlogSitemapEntries(options);
74
+ };
75
+ }
76
+ //# sourceMappingURL=sitemap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/next/sitemap.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,0CAA0C;AAC1C,gFAAgF;AAChF,6DAA6D;AAC7D,sDAAsD;AACtD,EAAE;AACF,SAAS;AACT,gDAAgD;AAChD,6DAA6D;AAC7D,EAAE;AACF,kDAAkD;AAClD,uFAAuF;AACvF,gCAAgC;AAChC,EAAE;AACF,+CAA+C;AAC/C,iEAAiE;AACjE,EAAE;AACF,8CAA8C;AAC9C,wDAAwD;AACxD,+CAA+C;AAC/C,wCAAwC;AACxC,UAAU;AACV,eAAe;AACf,kEAAkE;AAClE,wBAAwB;AACxB,SAAS;AACT,MAAM;AACN,gFAAgF;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqC3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAA2B;IAE3B,MAAM,EACJ,MAAM,EACN,UAAU,EACV,OAAO,EACP,QAAQ,GAAG,OAAO,EAClB,eAAe,GAAG,QAAQ,EAC1B,QAAQ,GAAG,GAAG,EACd,SAAS,GACV,GAAG,OAAO,CAAC;IAEZ,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC;IACrE,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC;IAE9B,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEpD,gCAAgC;IAChC,OAAO,CAAC,IAAI,CAAC;QACX,GAAG,EAAE,GAAG,cAAc,GAAG,cAAc,EAAE;QACzC,YAAY,EAAE,QAAQ,CAAC,SAAS;QAChC,eAAe;QACf,QAAQ,EAAE,QAAQ,GAAG,GAAG;KACzB,CAAC,CAAC;IAEH,oDAAoD;IACpD,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IACvC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,SAAS;QAExB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC;gBACX,GAAG,EAAE,GAAG,cAAc,GAAG,cAAc,IAAI,OAAO,CAAC,IAAI,EAAE;gBACzD,YAAY,EAAE,OAAO,CAAC,SAAS;gBAC/B,eAAe;gBACf,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,OAAO,KAAK,UAAU,OAAO;QAC3B,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earlyseo/blog",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Drop-in blog integration for React & Next.js — powered by EarlySEO. Articles are served from EarlySEO's CDN. Just add your site ID and render.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/init.mjs CHANGED
@@ -100,6 +100,27 @@ export const generateMetadata = createBlogPostMetadata({
100
100
  `;
101
101
  }
102
102
 
103
+ function blogSitemapPage(siteId) {
104
+ return `import { getBlogSitemapEntries } from "@earlyseo/blog/next";
105
+ import type { MetadataRoute } from "next";
106
+
107
+ const siteId = process.env.EARLYSEO_SITE_ID ?? "${siteId}";
108
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "https://example.com";
109
+
110
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
111
+ const blogEntries = await getBlogSitemapEntries({
112
+ siteId,
113
+ baseUrl,
114
+ });
115
+
116
+ return [
117
+ { url: baseUrl, lastModified: new Date() },
118
+ ...blogEntries,
119
+ ];
120
+ }
121
+ `;
122
+ }
123
+
103
124
  // ── Main ─────────────────────────────────────────────────────────────────────
104
125
 
105
126
  async function main() {
@@ -177,9 +198,14 @@ async function main() {
177
198
  await writeFileSafe(listPagePath, blogListPage(siteId));
178
199
  await writeFileSafe(postPagePath, blogPostPage(siteId));
179
200
 
180
- // 6. Add EARLYSEO_SITE_ID to .env.local
201
+ // 5b. Generate sitemap (at app root)
202
+ const sitemapPath = `${appDir}/sitemap.ts`;
203
+ await writeFileSafe(sitemapPath, blogSitemapPage(siteId));
204
+
205
+ // 6. Add EARLYSEO_SITE_ID and NEXT_PUBLIC_BASE_URL to .env.local
181
206
  const envPath = path.join(cwd, ".env.local");
182
207
  const envKey = "EARLYSEO_SITE_ID";
208
+ const baseUrlKey = "NEXT_PUBLIC_BASE_URL";
183
209
  if (fs.existsSync(envPath)) {
184
210
  const envContent = fs.readFileSync(envPath, "utf-8");
185
211
  if (envContent.includes(envKey)) {
@@ -188,9 +214,13 @@ async function main() {
188
214
  fs.appendFileSync(envPath, `\n# EarlySEO Blog SDK\n${envKey}=${siteId}\n`);
189
215
  console.log(` ✅ Added ${envKey} to .env.local`);
190
216
  }
217
+ if (!envContent.includes(baseUrlKey)) {
218
+ fs.appendFileSync(envPath, `${baseUrlKey}=https://example.com\n`);
219
+ console.log(` ✅ Added ${baseUrlKey} to .env.local (update with your site URL)`);
220
+ }
191
221
  } else {
192
- fs.writeFileSync(envPath, `# EarlySEO Blog SDK\n${envKey}=${siteId}\n`, "utf-8");
193
- console.log(` ✅ Created .env.local with ${envKey}`);
222
+ fs.writeFileSync(envPath, `# EarlySEO Blog SDK\n${envKey}=${siteId}\n${baseUrlKey}=https://example.com\n`, "utf-8");
223
+ console.log(` ✅ Created .env.local with ${envKey} and ${baseUrlKey}`);
194
224
  }
195
225
 
196
226
  // 7. Done
@@ -199,9 +229,11 @@ async function main() {
199
229
  console.log(" ✓ Setup complete! Your blog is ready at /blog");
200
230
  console.log();
201
231
  console.log(" Next steps:");
202
- console.log(" 1. Start your dev server: npm run dev");
203
- console.log(" 2. Visit: http://localhost:3000/blog");
204
- console.log(" 3. Publish articles from your EarlySEO dashboard");
232
+ console.log(" 1. Update NEXT_PUBLIC_BASE_URL in .env.local with your site URL");
233
+ console.log(" 2. Start your dev server: npm run dev");
234
+ console.log(" 3. Visit: http://localhost:3000/blog");
235
+ console.log(" 4. Sitemap available at: http://localhost:3000/sitemap.xml");
236
+ console.log(" 5. Publish articles from your EarlySEO dashboard");
205
237
  console.log();
206
238
  console.log(" Docs: https://www.npmjs.com/package/@earlyseo/blog");
207
239
  console.log();