nextblogkit 0.6.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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +951 -0
  3. package/dist/admin/index.cjs +2465 -0
  4. package/dist/admin/index.cjs.map +1 -0
  5. package/dist/admin/index.d.cts +44 -0
  6. package/dist/admin/index.d.ts +44 -0
  7. package/dist/admin/index.js +2438 -0
  8. package/dist/admin/index.js.map +1 -0
  9. package/dist/api/categories.cjs +82 -0
  10. package/dist/api/categories.cjs.map +1 -0
  11. package/dist/api/categories.d.cts +27 -0
  12. package/dist/api/categories.d.ts +27 -0
  13. package/dist/api/categories.js +77 -0
  14. package/dist/api/categories.js.map +1 -0
  15. package/dist/api/media.cjs +113 -0
  16. package/dist/api/media.cjs.map +1 -0
  17. package/dist/api/media.d.cts +22 -0
  18. package/dist/api/media.d.ts +22 -0
  19. package/dist/api/media.js +109 -0
  20. package/dist/api/media.js.map +1 -0
  21. package/dist/api/posts.cjs +103 -0
  22. package/dist/api/posts.cjs.map +1 -0
  23. package/dist/api/posts.d.cts +27 -0
  24. package/dist/api/posts.d.ts +27 -0
  25. package/dist/api/posts.js +98 -0
  26. package/dist/api/posts.js.map +1 -0
  27. package/dist/api/rss.cjs +25 -0
  28. package/dist/api/rss.cjs.map +1 -0
  29. package/dist/api/rss.d.cts +5 -0
  30. package/dist/api/rss.d.ts +5 -0
  31. package/dist/api/rss.js +23 -0
  32. package/dist/api/rss.js.map +1 -0
  33. package/dist/api/settings.cjs +40 -0
  34. package/dist/api/settings.cjs.map +1 -0
  35. package/dist/api/settings.d.cts +17 -0
  36. package/dist/api/settings.d.ts +17 -0
  37. package/dist/api/settings.js +37 -0
  38. package/dist/api/settings.js.map +1 -0
  39. package/dist/api/sitemap.cjs +25 -0
  40. package/dist/api/sitemap.cjs.map +1 -0
  41. package/dist/api/sitemap.d.cts +5 -0
  42. package/dist/api/sitemap.d.ts +5 -0
  43. package/dist/api/sitemap.js +23 -0
  44. package/dist/api/sitemap.js.map +1 -0
  45. package/dist/chunk-4NKOJYWJ.js +68 -0
  46. package/dist/chunk-4NKOJYWJ.js.map +1 -0
  47. package/dist/chunk-4PY224XM.js +103 -0
  48. package/dist/chunk-4PY224XM.js.map +1 -0
  49. package/dist/chunk-64HUVJOZ.js +446 -0
  50. package/dist/chunk-64HUVJOZ.js.map +1 -0
  51. package/dist/chunk-6HKMZOI4.cjs +48 -0
  52. package/dist/chunk-6HKMZOI4.cjs.map +1 -0
  53. package/dist/chunk-A2S32RZN.js +138 -0
  54. package/dist/chunk-A2S32RZN.js.map +1 -0
  55. package/dist/chunk-E2QLTHKN.cjs +70 -0
  56. package/dist/chunk-E2QLTHKN.cjs.map +1 -0
  57. package/dist/chunk-JLPJKNRZ.js +37 -0
  58. package/dist/chunk-JLPJKNRZ.js.map +1 -0
  59. package/dist/chunk-JM7QRXXK.js +330 -0
  60. package/dist/chunk-JM7QRXXK.js.map +1 -0
  61. package/dist/chunk-KDZER3PU.cjs +43 -0
  62. package/dist/chunk-KDZER3PU.cjs.map +1 -0
  63. package/dist/chunk-N5MKAD7J.cjs +109 -0
  64. package/dist/chunk-N5MKAD7J.cjs.map +1 -0
  65. package/dist/chunk-QE4VLQYN.cjs +337 -0
  66. package/dist/chunk-QE4VLQYN.cjs.map +1 -0
  67. package/dist/chunk-R6MO3QIP.js +46 -0
  68. package/dist/chunk-R6MO3QIP.js.map +1 -0
  69. package/dist/chunk-U2ROR6AY.cjs +476 -0
  70. package/dist/chunk-U2ROR6AY.cjs.map +1 -0
  71. package/dist/chunk-ZP5XRVVH.cjs +141 -0
  72. package/dist/chunk-ZP5XRVVH.cjs.map +1 -0
  73. package/dist/cli/index.cjs +1308 -0
  74. package/dist/components/index.cjs +541 -0
  75. package/dist/components/index.cjs.map +1 -0
  76. package/dist/components/index.d.cts +165 -0
  77. package/dist/components/index.d.ts +165 -0
  78. package/dist/components/index.js +527 -0
  79. package/dist/components/index.js.map +1 -0
  80. package/dist/editor/index.cjs +1083 -0
  81. package/dist/editor/index.cjs.map +1 -0
  82. package/dist/editor/index.d.cts +133 -0
  83. package/dist/editor/index.d.ts +133 -0
  84. package/dist/editor/index.js +1051 -0
  85. package/dist/editor/index.js.map +1 -0
  86. package/dist/index-Cgzphklp.d.ts +266 -0
  87. package/dist/index-vjlZDWNr.d.cts +266 -0
  88. package/dist/index.cjs +368 -0
  89. package/dist/index.cjs.map +1 -0
  90. package/dist/index.d.cts +27 -0
  91. package/dist/index.d.ts +27 -0
  92. package/dist/index.js +208 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/lib/index.cjs +120 -0
  95. package/dist/lib/index.cjs.map +1 -0
  96. package/dist/lib/index.d.cts +4 -0
  97. package/dist/lib/index.d.ts +4 -0
  98. package/dist/lib/index.js +7 -0
  99. package/dist/lib/index.js.map +1 -0
  100. package/dist/styles/admin.css +657 -0
  101. package/dist/styles/blog.css +851 -0
  102. package/dist/styles/editor.css +452 -0
  103. package/dist/styles/globals.css +270 -0
  104. package/dist/styles/prose.css +299 -0
  105. package/dist/types-CBEEBR4A.d.cts +732 -0
  106. package/dist/types-CBEEBR4A.d.ts +732 -0
  107. package/package.json +134 -0
package/README.md ADDED
@@ -0,0 +1,951 @@
1
+ # NextBlogKit
2
+
3
+ A complete blog engine for Next.js — admin panel, block editor, SEO, media storage, and more. Drop it into any Next.js 14+ app and get a fully featured blog in minutes.
4
+
5
+ ## Features
6
+
7
+ - **Block Editor** — TipTap-based with slash commands, image upload, code blocks (Shiki), callouts, FAQ schema, tables, task lists
8
+ - **Admin Panel** — Dashboard, post management, media library, category manager, settings — with configurable paths
9
+ - **SEO Engine** — Meta tags, Open Graph, Twitter Cards, JSON-LD structured data, sitemap.xml, RSS feed, Yoast-like SEO scorer
10
+ - **Image Pipeline** — Cloudflare R2 storage, automatic WebP conversion, responsive sizes, thumbnails
11
+ - **MongoDB Backend** — Full CRUD, slug generation, revision history, full-text search
12
+ - **Composable Components** — Page-level components with slot injection, or use individual atomic components to build your own layout
13
+ - **Table of Contents** — Sticky sidebar TOC with IntersectionObserver-based active heading tracking
14
+ - **Theming** — CSS variables for full color/font/spacing customization, dark mode support
15
+ - **CLI** — One command scaffolding for your Next.js app
16
+
17
+ ## Prerequisites
18
+
19
+ - **Next.js 14+** with App Router
20
+ - **MongoDB** (Atlas or self-hosted)
21
+ - **Cloudflare R2** bucket (for media storage)
22
+ - **Node.js 18+**
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Install
27
+
28
+ ```bash
29
+ pnpm add nextblogkit
30
+ ```
31
+
32
+ Or with npm/yarn:
33
+
34
+ ```bash
35
+ npm install nextblogkit
36
+ # or
37
+ yarn add nextblogkit
38
+ ```
39
+
40
+ ### 2. Scaffold into your Next.js project
41
+
42
+ ```bash
43
+ npx nextblogkit init
44
+ ```
45
+
46
+ This creates:
47
+ - `app/blog/` — Blog pages (list, post, category)
48
+ - `app/admin/blog/` — Admin panel pages (dashboard, posts, media, categories, settings)
49
+ - `app/api/blog/` — API routes (posts, media, categories, settings, sitemap, RSS)
50
+ - `nextblogkit.config.ts` — Configuration file
51
+ - `.env.local.example` — Environment variable template
52
+
53
+ ### 3. Configure environment variables
54
+
55
+ Copy the generated `.env.local.example` to `.env.local`:
56
+
57
+ ```bash
58
+ cp .env.local.example .env.local
59
+ ```
60
+
61
+ Fill in your values:
62
+
63
+ ```env
64
+ # MongoDB Connection
65
+ NEXTBLOGKIT_MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/mydb
66
+
67
+ # Cloudflare R2 Storage
68
+ NEXTBLOGKIT_R2_ACCOUNT_ID=your-account-id
69
+ NEXTBLOGKIT_R2_ACCESS_KEY=your-access-key
70
+ NEXTBLOGKIT_R2_SECRET_KEY=your-secret-key
71
+ NEXTBLOGKIT_R2_BUCKET=blog-media
72
+ NEXTBLOGKIT_R2_PUBLIC_URL=https://media.yourdomain.com
73
+
74
+ # Authentication
75
+ NEXTBLOGKIT_API_KEY=your-secure-api-key-must-be-at-least-32-characters-long
76
+
77
+ # Site Info
78
+ NEXTBLOGKIT_SITE_URL=https://yourdomain.com
79
+ NEXTBLOGKIT_SITE_NAME="Your Site Name"
80
+ ```
81
+
82
+ ### 4. Run database migrations
83
+
84
+ ```bash
85
+ npx nextblogkit migrate
86
+ ```
87
+
88
+ This creates the required MongoDB indexes for posts, categories, and media.
89
+
90
+ ### 5. Start your app
91
+
92
+ ```bash
93
+ pnpm dev
94
+ ```
95
+
96
+ - Blog: [http://localhost:3000/blog](http://localhost:3000/blog)
97
+ - Admin: [http://localhost:3000/admin/blog](http://localhost:3000/admin/blog)
98
+
99
+ The admin panel will prompt you for the API key on first visit (the value of `NEXTBLOGKIT_API_KEY`).
100
+
101
+ ---
102
+
103
+ ## Configuration
104
+
105
+ The `nextblogkit.config.ts` file controls all behavior:
106
+
107
+ ```typescript
108
+ import { defineConfig } from 'nextblogkit';
109
+
110
+ export default defineConfig({
111
+ // URL paths — customize to match your site structure
112
+ basePath: '/blog', // Public blog URL prefix
113
+ adminPath: '/admin/blog', // Admin panel URL prefix
114
+ apiPath: '/api/blog', // API routes URL prefix
115
+
116
+ // Pagination
117
+ postsPerPage: 10,
118
+ excerptLength: 160,
119
+
120
+ // Editor settings
121
+ editor: {
122
+ blocks: ['paragraph', 'heading', 'image', 'codeBlock', 'blockquote',
123
+ 'bulletList', 'orderedList', 'taskList', 'table', 'callout',
124
+ 'tableOfContents', 'faq', 'horizontalRule'],
125
+ maxImageSize: 10 * 1024 * 1024, // 10MB
126
+ autosaveInterval: 30000, // 30s
127
+ },
128
+
129
+ // SEO
130
+ seo: {
131
+ titleTemplate: '%s | %siteName%',
132
+ generateRSS: true,
133
+ generateSitemap: true,
134
+ structuredData: true,
135
+ },
136
+
137
+ // Feature toggles
138
+ features: {
139
+ search: true,
140
+ relatedPosts: true,
141
+ readingProgress: true,
142
+ tableOfContents: true,
143
+ shareButtons: true,
144
+ darkMode: true,
145
+ scheduling: true,
146
+ revisionHistory: true,
147
+ imageOptimization: true,
148
+ },
149
+
150
+ // Authentication
151
+ auth: {
152
+ strategy: 'api-key',
153
+ },
154
+ });
155
+ ```
156
+
157
+ ### Custom URL Paths
158
+
159
+ You can change the blog URL from `/blog` to anything — `/articles`, `/posts`, `/blogs`, etc. Make sure the folder structure in your `app/` directory matches and pass the paths through to components:
160
+
161
+ ```typescript
162
+ export default defineConfig({
163
+ basePath: '/articles',
164
+ adminPath: '/admin/articles',
165
+ apiPath: '/api/articles',
166
+ });
167
+ ```
168
+
169
+ Then in your admin layout, pass the paths:
170
+
171
+ ```tsx
172
+ // app/admin/articles/layout.tsx
173
+ import { AdminLayout } from 'nextblogkit/admin';
174
+ import 'nextblogkit/styles/admin.css';
175
+
176
+ export default function AdminBlogLayout({ children }: { children: React.ReactNode }) {
177
+ return (
178
+ <AdminLayout apiPath="/api/articles" adminPath="/admin/articles">
179
+ {children}
180
+ </AdminLayout>
181
+ );
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Theming
188
+
189
+ NextBlogKit uses CSS variables for theming. Every color, font, radius, and shadow is a `--nbk-*` variable that you can override.
190
+
191
+ ### All CSS Variables
192
+
193
+ | Variable | Default | Description |
194
+ |----------|---------|-------------|
195
+ | `--nbk-primary` | `#2563eb` | Primary brand color (links, buttons, active states) |
196
+ | `--nbk-primary-hover` | `#1d4ed8` | Primary hover state |
197
+ | `--nbk-primary-light` | `#dbeafe` | Light primary (tag backgrounds, highlights) |
198
+ | `--nbk-text` | `#1f2937` | Main text color |
199
+ | `--nbk-text-muted` | `#6b7280` | Secondary/muted text |
200
+ | `--nbk-bg` | `#ffffff` | Page background |
201
+ | `--nbk-bg-secondary` | `#f9fafb` | Secondary background (cards, sidebar) |
202
+ | `--nbk-card-bg` | `#ffffff` | Card background |
203
+ | `--nbk-border` | `#e5e7eb` | Border color |
204
+ | `--nbk-border-focus` | `#2563eb` | Input focus border |
205
+ | `--nbk-radius` | `0.5rem` | Border radius |
206
+ | `--nbk-shadow` | `0 1px 3px ...` | Default box shadow |
207
+ | `--nbk-shadow-sm` | `0 1px 2px ...` | Small shadow |
208
+ | `--nbk-shadow-lg` | `0 10px 15px ...` | Large shadow |
209
+ | `--nbk-shadow-xl` | `0 20px 25px ...` | Extra large shadow |
210
+ | `--nbk-font-heading` | `"Inter", system-ui, sans-serif` | Heading font family |
211
+ | `--nbk-font-body` | `"Inter", system-ui, sans-serif` | Body font family |
212
+ | `--nbk-font-code` | `"JetBrains Mono", monospace` | Code font family |
213
+ | `--nbk-success` | `#10b981` | Success color |
214
+ | `--nbk-warning` | `#f59e0b` | Warning color |
215
+ | `--nbk-danger` | `#ef4444` | Danger/error color |
216
+ | `--nbk-info` | `#3b82f6` | Info color |
217
+ | `--nbk-focus-ring` | `0 0 0 3px rgba(...)` | Focus ring box-shadow |
218
+
219
+ ### Override via CSS
220
+
221
+ Add a `<style>` block in your blog layout or override in your global CSS:
222
+
223
+ ```tsx
224
+ // app/blog/layout.tsx
225
+ import 'nextblogkit/styles/blog.css';
226
+
227
+ export default function BlogLayout({ children }: { children: React.ReactNode }) {
228
+ return (
229
+ <>
230
+ <style dangerouslySetInnerHTML={{ __html: `
231
+ :root {
232
+ --nbk-primary: #DC2626;
233
+ --nbk-primary-hover: #B91C1C;
234
+ --nbk-primary-light: #FEE2E2;
235
+ --nbk-bg-secondary: #FEF2F2;
236
+ --nbk-border-focus: #DC2626;
237
+ --nbk-font-heading: "Poppins", system-ui, sans-serif;
238
+ --nbk-font-body: "Inter", system-ui, sans-serif;
239
+ }
240
+ `}} />
241
+ {children}
242
+ </>
243
+ );
244
+ }
245
+ ```
246
+
247
+ ### Color Presets
248
+
249
+ Here are some ready-to-use brand palettes:
250
+
251
+ **Red**
252
+ ```css
253
+ --nbk-primary: #DC2626;
254
+ --nbk-primary-hover: #B91C1C;
255
+ --nbk-primary-light: #FEE2E2;
256
+ --nbk-bg-secondary: #FEF2F2;
257
+ ```
258
+
259
+ **Teal**
260
+ ```css
261
+ --nbk-primary: #0891B2;
262
+ --nbk-primary-hover: #0E7490;
263
+ --nbk-primary-light: #CCFBF1;
264
+ --nbk-bg-secondary: #F0FDFA;
265
+ ```
266
+
267
+ **Purple**
268
+ ```css
269
+ --nbk-primary: #7C3AED;
270
+ --nbk-primary-hover: #6D28D9;
271
+ --nbk-primary-light: #EDE9FE;
272
+ --nbk-bg-secondary: #F5F3FF;
273
+ ```
274
+
275
+ **Green**
276
+ ```css
277
+ --nbk-primary: #059669;
278
+ --nbk-primary-hover: #047857;
279
+ --nbk-primary-light: #D1FAE5;
280
+ --nbk-bg-secondary: #ECFDF5;
281
+ ```
282
+
283
+ **Orange**
284
+ ```css
285
+ --nbk-primary: #EA580C;
286
+ --nbk-primary-hover: #C2410C;
287
+ --nbk-primary-light: #FFEDD5;
288
+ --nbk-bg-secondary: #FFF7ED;
289
+ ```
290
+
291
+ ### Dark Mode
292
+
293
+ Dark mode variables are automatically applied via `prefers-color-scheme: dark`. Override them in a media query:
294
+
295
+ ```css
296
+ @media (prefers-color-scheme: dark) {
297
+ :root {
298
+ --nbk-bg: #0f172a;
299
+ --nbk-bg-secondary: #1e293b;
300
+ --nbk-text: #f1f5f9;
301
+ --nbk-text-muted: #94a3b8;
302
+ --nbk-border: #334155;
303
+ --nbk-card-bg: #1e293b;
304
+ }
305
+ }
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Components
311
+
312
+ ### Page-Level Components
313
+
314
+ These are full-page components with built-in layouts, designed to be used directly in your route files. They accept `slots` for injecting custom content without rebuilding the entire page.
315
+
316
+ #### `BlogPostPage`
317
+
318
+ Renders a complete blog post with header, content, TOC, author card, related posts, and share buttons.
319
+
320
+ ```tsx
321
+ import { BlogPostPage } from 'nextblogkit/components';
322
+ ```
323
+
324
+ **Props:**
325
+
326
+ | Prop | Type | Default | Description |
327
+ |------|------|---------|-------------|
328
+ | `post` | `object` | *required* | The blog post object (from `getPostBySlug`) |
329
+ | `relatedPosts` | `object[]` | `[]` | Related posts to show at the bottom |
330
+ | `showTOC` | `boolean` | `true` | Show table of contents |
331
+ | `tocPosition` | `'sidebar' \| 'top' \| 'none'` | `'sidebar'` | Where to render the TOC |
332
+ | `showAuthor` | `boolean` | `true` | Show author card |
333
+ | `showRelatedPosts` | `boolean` | `true` | Show related posts section |
334
+ | `showShareButtons` | `boolean` | `true` | Show share buttons |
335
+ | `showReadingProgress` | `boolean` | `true` | Show reading progress bar at top |
336
+ | `basePath` | `string` | `'/blog'` | Blog URL prefix (for links) |
337
+ | `className` | `string` | `''` | Additional CSS class |
338
+ | `slots` | `BlogPostSlots` | — | Custom content injection (see below) |
339
+
340
+ **TOC Positions:**
341
+
342
+ - `'sidebar'` — Sticky sidebar on the left, visible on screens >= 1024px. Best for long-form content.
343
+ - `'top'` — Inline TOC above the post content. Good for simpler layouts.
344
+ - `'none'` — No TOC rendered at all.
345
+
346
+ The TOC only renders if the post has more than 2 headings (H2/H3/H4).
347
+
348
+ **Slots:**
349
+
350
+ ```typescript
351
+ interface BlogPostSlots {
352
+ header?: React.ReactNode; // Above the article
353
+ footer?: React.ReactNode; // Below the article
354
+ beforeContent?: React.ReactNode; // Between cover image and post body
355
+ afterContent?: React.ReactNode; // After post body, before tags
356
+ }
357
+ ```
358
+
359
+ **Example with slots:**
360
+
361
+ ```tsx
362
+ <BlogPostPage
363
+ post={post}
364
+ relatedPosts={relatedPosts}
365
+ showTOC
366
+ tocPosition="sidebar"
367
+ basePath="/blogs"
368
+ slots={{
369
+ header: <SiteHeader />,
370
+ footer: <SiteFooter />,
371
+ beforeContent: <div className="ad-banner">Sponsored</div>,
372
+ afterContent: <NewsletterSignup />,
373
+ }}
374
+ />
375
+ ```
376
+
377
+ **Full page example:**
378
+
379
+ ```tsx
380
+ // app/blog/[slug]/page.tsx
381
+ import { BlogPostPage } from 'nextblogkit/components';
382
+ import { getPostBySlug, listPosts, generateMetaTags } from 'nextblogkit/lib';
383
+ import type { Metadata } from 'next';
384
+
385
+ interface Props {
386
+ params: Promise<{ slug: string }>;
387
+ }
388
+
389
+ export async function generateMetadata({ params }: Props): Promise<Metadata> {
390
+ const { slug } = await params;
391
+ const post = await getPostBySlug(slug);
392
+ if (!post) return { title: 'Post Not Found' };
393
+ const meta = generateMetaTags(post);
394
+ return {
395
+ title: meta.title,
396
+ description: meta.description,
397
+ openGraph: meta.openGraph,
398
+ twitter: meta.twitter,
399
+ alternates: { canonical: meta.canonical },
400
+ };
401
+ }
402
+
403
+ export default async function BlogPost({ params }: Props) {
404
+ const { slug } = await params;
405
+ const post = await getPostBySlug(slug);
406
+
407
+ if (!post) {
408
+ return <div className="nbk-not-found">Post not found</div>;
409
+ }
410
+
411
+ let relatedPosts: any[] = [];
412
+ if (post.categories?.length) {
413
+ const { posts } = await listPosts({
414
+ status: 'published',
415
+ category: post.categories[0],
416
+ limit: 4,
417
+ });
418
+ relatedPosts = posts
419
+ .filter((p) => String(p._id) !== String(post._id))
420
+ .slice(0, 3);
421
+ }
422
+
423
+ return (
424
+ <BlogPostPage
425
+ post={JSON.parse(JSON.stringify(post))}
426
+ relatedPosts={JSON.parse(JSON.stringify(relatedPosts))}
427
+ showTOC
428
+ tocPosition="sidebar"
429
+ basePath="/blog"
430
+ />
431
+ );
432
+ }
433
+ ```
434
+
435
+ ---
436
+
437
+ #### `BlogListPage`
438
+
439
+ Renders a blog listing with search, categories, pagination, and grid/list/magazine layouts.
440
+
441
+ ```tsx
442
+ import { BlogListPage } from 'nextblogkit/components';
443
+ ```
444
+
445
+ **Props:**
446
+
447
+ | Prop | Type | Default | Description |
448
+ |------|------|---------|-------------|
449
+ | `posts` | `object[]` | *required* | Array of post objects |
450
+ | `total` | `number` | *required* | Total number of posts (for pagination) |
451
+ | `page` | `number` | `1` | Current page number |
452
+ | `postsPerPage` | `number` | `10` | Posts per page |
453
+ | `categories` | `object[]` | `[]` | List of categories |
454
+ | `activeCategory` | `string` | — | Currently filtered category slug |
455
+ | `showCategories` | `boolean` | `true` | Show category sidebar |
456
+ | `showSearch` | `boolean` | `true` | Show search bar |
457
+ | `layout` | `'grid' \| 'list' \| 'magazine'` | `'grid'` | Post card layout style |
458
+ | `basePath` | `string` | `'/blog'` | Blog URL prefix |
459
+ | `apiPath` | `string` | `'/api/blog'` | API URL prefix (for search) |
460
+ | `className` | `string` | `''` | Additional CSS class |
461
+ | `slots` | `BlogListSlots` | — | Custom content injection (see below) |
462
+
463
+ **Slots:**
464
+
465
+ ```typescript
466
+ interface BlogListSlots {
467
+ header?: React.ReactNode; // Above the blog list
468
+ footer?: React.ReactNode; // Below the blog list
469
+ beforePosts?: React.ReactNode; // Between search bar and posts grid
470
+ afterPosts?: React.ReactNode; // After posts grid, before footer
471
+ sidebar?: React.ReactNode; // Replaces the default category sidebar
472
+ renderCard?: (post: any) => React.ReactNode; // Custom card renderer
473
+ }
474
+ ```
475
+
476
+ **Example with custom card:**
477
+
478
+ ```tsx
479
+ <BlogListPage
480
+ posts={posts}
481
+ total={total}
482
+ basePath="/blogs"
483
+ apiPath="/api/blogs"
484
+ layout="grid"
485
+ slots={{
486
+ header: <h1 className="text-3xl font-bold">Our Blog</h1>,
487
+ renderCard: (post) => (
488
+ <div key={post.slug} className="my-custom-card">
489
+ <h3>{post.title}</h3>
490
+ <p>{post.excerpt}</p>
491
+ </div>
492
+ ),
493
+ sidebar: (
494
+ <div>
495
+ <h3>Newsletter</h3>
496
+ <NewsletterSignup />
497
+ </div>
498
+ ),
499
+ }}
500
+ />
501
+ ```
502
+
503
+ ---
504
+
505
+ ### Atomic Components
506
+
507
+ All individual components are exported from `nextblogkit/components` and can be used to build custom layouts:
508
+
509
+ ```tsx
510
+ import {
511
+ BlogCard,
512
+ BlogSearch,
513
+ TableOfContents,
514
+ ShareButtons,
515
+ ReadingProgressBar,
516
+ Pagination,
517
+ AuthorCard,
518
+ BreadcrumbNav,
519
+ CategoryList,
520
+ TagCloud,
521
+ CodeBlock,
522
+ } from 'nextblogkit/components';
523
+ ```
524
+
525
+ | Component | Description |
526
+ |-----------|-------------|
527
+ | `BlogCard` | Post preview card (vertical or horizontal layout) |
528
+ | `BlogSearch` | Search input with instant results dropdown |
529
+ | `TableOfContents` | Heading list with scroll-to and active heading tracking |
530
+ | `ShareButtons` | Social share buttons (Twitter, Facebook, LinkedIn, copy link) |
531
+ | `ReadingProgressBar` | Top-of-page progress bar tied to scroll position |
532
+ | `Pagination` | Page navigation with prev/next and page numbers |
533
+ | `AuthorCard` | Author info card with avatar and bio |
534
+ | `BreadcrumbNav` | Breadcrumb trail navigation |
535
+ | `CategoryList` | Category list with post counts |
536
+ | `TagCloud` | Tag cloud with weighted sizes |
537
+ | `CodeBlock` | Syntax-highlighted code with Shiki |
538
+
539
+ ---
540
+
541
+ ### Admin Components
542
+
543
+ ```tsx
544
+ import {
545
+ AdminLayout,
546
+ Dashboard,
547
+ PostList,
548
+ PostEditor,
549
+ MediaLibrary,
550
+ CategoryManager,
551
+ SettingsPage,
552
+ SEOPanel,
553
+ useAdminApi,
554
+ setApiBase,
555
+ } from 'nextblogkit/admin';
556
+ ```
557
+
558
+ #### `AdminLayout`
559
+
560
+ Wraps all admin pages with sidebar navigation and authentication.
561
+
562
+ | Prop | Type | Default | Description |
563
+ |------|------|---------|-------------|
564
+ | `children` | `ReactNode` | *required* | Page content |
565
+ | `apiKey` | `string` | — | Pre-set API key (bypasses login prompt) |
566
+ | `apiPath` | `string` | `'/api/blog'` | API route prefix for all admin API calls |
567
+ | `adminPath` | `string` | `'/admin/blog'` | Admin route prefix for sidebar nav links |
568
+
569
+ **Important:** If you change `apiPath` in your config, you **must** pass it to `AdminLayout`:
570
+
571
+ ```tsx
572
+ // app/admin/blog/layout.tsx
573
+ import { AdminLayout } from 'nextblogkit/admin';
574
+ import 'nextblogkit/styles/admin.css';
575
+
576
+ export default function AdminBlogLayout({ children }: { children: React.ReactNode }) {
577
+ return (
578
+ <AdminLayout apiPath="/api/blogs" adminPath="/admin/blog">
579
+ {children}
580
+ </AdminLayout>
581
+ );
582
+ }
583
+ ```
584
+
585
+ ---
586
+
587
+ ## Styles
588
+
589
+ Import the CSS files you need in your layout files:
590
+
591
+ ```tsx
592
+ // Blog pages — import in app/blog/layout.tsx
593
+ import 'nextblogkit/styles/blog.css';
594
+
595
+ // Admin pages — import in app/admin/blog/layout.tsx
596
+ import 'nextblogkit/styles/admin.css';
597
+ ```
598
+
599
+ | Stylesheet | When to import | Description |
600
+ |-----------|----------------|-------------|
601
+ | `nextblogkit/styles/blog.css` | Blog layout | Blog list, post page, TOC, cards, pagination |
602
+ | `nextblogkit/styles/admin.css` | Admin layout | Admin panel, dashboard, forms, tables |
603
+ | `nextblogkit/styles/editor.css` | Admin layout (auto) | TipTap editor styles |
604
+ | `nextblogkit/styles/globals.css` | Root layout (optional) | CSS variables, utility classes, buttons, forms |
605
+ | `nextblogkit/styles/prose.css` | Blog layout (optional) | Post body typography |
606
+
607
+ The `blog.css` and `admin.css` files already include the global variables. You only need `globals.css` if using atomic components outside of the page-level components.
608
+
609
+ ---
610
+
611
+ ## Package Exports
612
+
613
+ ```typescript
614
+ // Main exports — config, types, defineConfig
615
+ import { defineConfig } from 'nextblogkit';
616
+ import type { BlogPost, Category, NextBlogKitConfig } from 'nextblogkit';
617
+
618
+ // Library — server-side utilities
619
+ import { getDb, generateMetaTags, searchPosts, calculateSEOScore } from 'nextblogkit/lib';
620
+
621
+ // Blog components — client-side React components
622
+ import { BlogListPage, BlogPostPage, BlogCard, BlogSearch } from 'nextblogkit/components';
623
+ import { TableOfContents, ShareButtons, ReadingProgressBar } from 'nextblogkit/components';
624
+ import { Pagination, AuthorCard, BreadcrumbNav, CategoryList } from 'nextblogkit/components';
625
+ import type { BlogListSlots, BlogPostSlots } from 'nextblogkit/components';
626
+
627
+ // Admin panel — client-side React components
628
+ import { AdminLayout, Dashboard, PostList, PostEditor } from 'nextblogkit/admin';
629
+ import { MediaLibrary, CategoryManager, SettingsPage, SEOPanel } from 'nextblogkit/admin';
630
+
631
+ // Editor — TipTap block editor
632
+ import { BlogEditor } from 'nextblogkit/editor';
633
+
634
+ // API route handlers — re-export in your route.ts files
635
+ export { GET, POST, PUT, DELETE } from 'nextblogkit/api/posts';
636
+ export { GET, POST, DELETE } from 'nextblogkit/api/media';
637
+ export { GET, POST, PUT, DELETE } from 'nextblogkit/api/categories';
638
+ export { GET, PUT } from 'nextblogkit/api/settings';
639
+ export { GET } from 'nextblogkit/api/sitemap';
640
+ export { GET } from 'nextblogkit/api/rss';
641
+
642
+ // Styles
643
+ import 'nextblogkit/styles/blog.css';
644
+ import 'nextblogkit/styles/admin.css';
645
+ import 'nextblogkit/styles/editor.css';
646
+ import 'nextblogkit/styles/prose.css';
647
+ import 'nextblogkit/styles/globals.css';
648
+ ```
649
+
650
+ ---
651
+
652
+ ## Integrating with Your Site
653
+
654
+ A common pattern is wrapping the blog in your site's header, footer, and theme. Here's a full example:
655
+
656
+ ```tsx
657
+ // app/blogs/layout.tsx
658
+ import 'nextblogkit/styles/blog.css';
659
+ import SiteHeader from '@/components/SiteHeader';
660
+ import SiteFooter from '@/components/SiteFooter';
661
+
662
+ export default function BlogLayout({ children }: { children: React.ReactNode }) {
663
+ return (
664
+ <>
665
+ {/* Override nextblogkit colors to match your brand */}
666
+ <style dangerouslySetInnerHTML={{ __html: `
667
+ :root {
668
+ --nbk-primary: #DC2626;
669
+ --nbk-primary-hover: #B91C1C;
670
+ --nbk-primary-light: #FEE2E2;
671
+ --nbk-bg-secondary: #FEF2F2;
672
+ --nbk-border-focus: #DC2626;
673
+ --nbk-font-heading: var(--font-geist-sans), system-ui, sans-serif;
674
+ --nbk-font-body: var(--font-geist-sans), system-ui, sans-serif;
675
+ }
676
+ `}} />
677
+ <SiteHeader />
678
+ <div style={{ paddingTop: '4rem' }}>
679
+ {children}
680
+ </div>
681
+ <SiteFooter />
682
+ </>
683
+ );
684
+ }
685
+ ```
686
+
687
+ ---
688
+
689
+ ## API Routes
690
+
691
+ All API routes require a Bearer token (`NEXTBLOGKIT_API_KEY`) for write operations. GET requests for public data (published posts, categories) are unauthenticated.
692
+
693
+ ### Posts
694
+
695
+ | Method | Endpoint | Description |
696
+ |--------|----------|-------------|
697
+ | GET | `/api/blog/posts` | List posts (supports `?status=`, `?category=`, `?search=`, `?page=`, `?limit=`) |
698
+ | GET | `/api/blog/posts?slug=my-post` | Get single post by slug |
699
+ | GET | `/api/blog/posts?id=abc123` | Get single post by ID |
700
+ | POST | `/api/blog/posts` | Create post |
701
+ | PUT | `/api/blog/posts` | Update post (requires `?id=`) |
702
+ | DELETE | `/api/blog/posts` | Delete/archive post (requires `?id=`) |
703
+
704
+ ### Media
705
+
706
+ | Method | Endpoint | Description |
707
+ |--------|----------|-------------|
708
+ | GET | `/api/blog/media` | List media files |
709
+ | POST | `/api/blog/media` | Upload file (multipart/form-data) |
710
+ | DELETE | `/api/blog/media?id=abc123` | Delete media file |
711
+
712
+ ### Categories
713
+
714
+ | Method | Endpoint | Description |
715
+ |--------|----------|-------------|
716
+ | GET | `/api/blog/categories` | List categories |
717
+ | POST | `/api/blog/categories` | Create category |
718
+ | PUT | `/api/blog/categories?id=abc123` | Update category |
719
+ | DELETE | `/api/blog/categories?id=abc123` | Delete category |
720
+
721
+ ### Settings
722
+
723
+ | Method | Endpoint | Description |
724
+ |--------|----------|-------------|
725
+ | GET | `/api/blog/settings` | Get settings |
726
+ | PUT | `/api/blog/settings` | Update settings |
727
+
728
+ > **Note:** These endpoints use the default `/api/blog` prefix. If you changed `apiPath` in your config, replace `/api/blog` with your custom path.
729
+
730
+ ### Authentication
731
+
732
+ Include the API key as a Bearer token:
733
+
734
+ ```bash
735
+ curl -X POST http://localhost:3000/api/blog/posts \
736
+ -H "Authorization: Bearer your-api-key-here" \
737
+ -H "Content-Type: application/json" \
738
+ -d '{"title": "My Post", "content": {...}}'
739
+ ```
740
+
741
+ ---
742
+
743
+ ## Editor Slash Commands
744
+
745
+ Type `/` in the editor to open the command menu:
746
+
747
+ | Command | Description |
748
+ |---------|-------------|
749
+ | Heading 2 | Section heading |
750
+ | Heading 3 | Subsection heading |
751
+ | Bullet List | Unordered list |
752
+ | Numbered List | Ordered list |
753
+ | Task List | Checklist with checkboxes |
754
+ | Blockquote | Quote block |
755
+ | Code Block | Syntax-highlighted code |
756
+ | Image | Upload an image |
757
+ | Table | Insert a 3x3 table |
758
+ | Divider | Horizontal rule |
759
+ | Callout | Info/warning/tip/danger box |
760
+ | FAQ | FAQ section with schema markup |
761
+ | Table of Contents | Auto-generated TOC |
762
+
763
+ ---
764
+
765
+ ## Custom Image Upload
766
+
767
+ Pass a custom upload handler to the editor:
768
+
769
+ ```tsx
770
+ import { BlogEditor } from 'nextblogkit/editor';
771
+
772
+ function MyEditor() {
773
+ const handleUpload = async (file: File) => {
774
+ const formData = new FormData();
775
+ formData.append('file', file);
776
+
777
+ const res = await fetch('/api/blog/media', {
778
+ method: 'POST',
779
+ headers: { Authorization: `Bearer ${apiKey}` },
780
+ body: formData,
781
+ });
782
+
783
+ const data = await res.json();
784
+ return { url: data.url, alt: file.name };
785
+ };
786
+
787
+ return (
788
+ <BlogEditor
789
+ uploadImage={handleUpload}
790
+ onChange={(content) => console.log(content)}
791
+ placeholder="Start writing..."
792
+ />
793
+ );
794
+ }
795
+ ```
796
+
797
+ ---
798
+
799
+ ## SEO
800
+
801
+ NextBlogKit automatically generates:
802
+
803
+ - **Meta tags** — Title, description, canonical URL
804
+ - **Open Graph** — og:title, og:description, og:image, og:type
805
+ - **Twitter Cards** — twitter:card, twitter:title, twitter:description
806
+ - **JSON-LD** — BlogPosting and FAQPage structured data
807
+ - **Sitemap** — Dynamic XML sitemap at `/api/blog/sitemap.xml`
808
+ - **RSS** — RSS 2.0 feed at `/api/blog/rss.xml`
809
+
810
+ The built-in SEO scorer checks 17 factors including keyword density, title length, heading hierarchy, image alt text, and readability.
811
+
812
+ ---
813
+
814
+ ## CLI Commands
815
+
816
+ ```bash
817
+ npx nextblogkit init # Scaffold blog into your Next.js project
818
+ npx nextblogkit seed # Insert example blog content
819
+ npx nextblogkit health # Check MongoDB and R2 connectivity
820
+ npx nextblogkit migrate # Create database indexes
821
+ ```
822
+
823
+ ### Init options
824
+
825
+ ```bash
826
+ npx nextblogkit init --blog-path /articles --admin-path /admin/articles --api-path /api/articles
827
+ ```
828
+
829
+ ---
830
+
831
+ ## What Gets Scaffolded
832
+
833
+ Running `npx nextblogkit init` creates thin wrapper files inside your `app/` directory. Each file is a one-liner that re-exports from the package:
834
+
835
+ ```
836
+ app/
837
+ ├── blog/
838
+ │ ├── page.tsx # Blog list page
839
+ │ ├── [slug]/page.tsx # Individual post page (with SEO metadata)
840
+ │ └── category/[slug]/page.tsx # Category filter page
841
+ ├── admin/blog/
842
+ │ ├── layout.tsx # Admin layout with sidebar
843
+ │ ├── page.tsx # Dashboard
844
+ │ ├── posts/page.tsx # Post list
845
+ │ ├── new/page.tsx # New post editor
846
+ │ ├── [id]/edit/page.tsx # Edit post
847
+ │ ├── media/page.tsx # Media library
848
+ │ ├── categories/page.tsx # Category manager
849
+ │ └── settings/page.tsx # Settings
850
+ └── api/blog/
851
+ ├── posts/route.ts # Posts CRUD
852
+ ├── media/route.ts # Media upload/list/delete
853
+ ├── categories/route.ts # Categories CRUD
854
+ ├── settings/route.ts # Settings read/update
855
+ ├── sitemap.xml/route.ts # Dynamic sitemap
856
+ └── rss.xml/route.ts # RSS feed
857
+ ```
858
+
859
+ Because these are thin wrappers, you can customize any page by editing the generated file directly.
860
+
861
+ ---
862
+
863
+ ## Local Development (without R2)
864
+
865
+ If you want to try NextBlogKit locally without Cloudflare R2, images will fall back to `URL.createObjectURL` (browser-only, non-persistent). For a fully working local setup:
866
+
867
+ 1. **MongoDB** — Use a free [MongoDB Atlas](https://www.mongodb.com/atlas) cluster, or run locally:
868
+ ```bash
869
+ # Using Docker
870
+ docker run -d -p 27017:27017 --name mongo mongo:7
871
+ # Then use: NEXTBLOGKIT_MONGODB_URI=mongodb://localhost:27017/nextblogkit
872
+ ```
873
+
874
+ 2. **R2** — Create a free Cloudflare R2 bucket at [dash.cloudflare.com](https://dash.cloudflare.com). The free tier includes 10 GB storage and 10 million reads/month.
875
+
876
+ 3. **API Key** — Generate a secure key:
877
+ ```bash
878
+ openssl rand -hex 32
879
+ ```
880
+
881
+ ---
882
+
883
+ ## Project Structure (package internals)
884
+
885
+ ```
886
+ src/
887
+ ├── lib/ # Server-side: DB, storage, config, SEO, search, utils
888
+ ├── api/ # Next.js API route handlers
889
+ ├── editor/ # TipTap editor + extensions (client-side)
890
+ ├── admin/ # Admin panel components (client-side)
891
+ ├── components/ # Public blog components (client-side)
892
+ ├── styles/ # CSS files
893
+ ├── cli/ # CLI tool (init, seed, health, migrate)
894
+ └── index.ts # Main entry point
895
+ ```
896
+
897
+ ---
898
+
899
+ ## Developing NextBlogKit Itself
900
+
901
+ If you want to contribute or develop locally:
902
+
903
+ ```bash
904
+ git clone https://github.com/patidar-santosh/nextblogkit.git
905
+ cd nextblogkit
906
+ pnpm install
907
+ pnpm run build # Build the package
908
+ pnpm run dev # Watch mode for development
909
+ ```
910
+
911
+ ### Using a local build in a Next.js project
912
+
913
+ ```bash
914
+ # Build, pack, and install
915
+ cd /path/to/nextblogkit
916
+ npm run build
917
+ cp -r src/styles dist/styles
918
+ npm pack
919
+
920
+ # In your Next.js project
921
+ cd /path/to/my-nextjs-app
922
+ pnpm install /path/to/nextblogkit/nextblogkit-0.6.0.tgz
923
+ ```
924
+
925
+ After installing, clear the Next.js cache:
926
+
927
+ ```bash
928
+ rm -rf .next
929
+ pnpm dev
930
+ ```
931
+
932
+ ---
933
+
934
+ ## Tech Stack
935
+
936
+ | Layer | Technology |
937
+ |-------|-----------|
938
+ | Framework | Next.js 14+ (App Router) |
939
+ | Editor | TipTap 2.x |
940
+ | Database | MongoDB (native driver) |
941
+ | Storage | Cloudflare R2 (S3-compatible) |
942
+ | Image Processing | Sharp |
943
+ | Syntax Highlighting | Shiki |
944
+ | Validation | Zod |
945
+ | CLI | Commander.js |
946
+ | Build | tsup (ESM + CJS) |
947
+ | Styling | CSS Variables + Tailwind-compatible |
948
+
949
+ ## License
950
+
951
+ MIT