nextblogkit 0.7.2 → 0.7.4

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
@@ -2,6 +2,8 @@
2
2
 
3
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
4
 
5
+ > **Sponsored by [ButterBlogs](https://butterblogs.com)** — AI-powered content creation, smooth as butter. Research, write, optimize, and publish SEO-ready blog posts 10x faster. NextBlogKit + ButterBlogs = write with AI, publish with one click.
6
+
5
7
  ## Features
6
8
 
7
9
  - **Block Editor** — TipTap-based with slash commands, image upload, code blocks (Shiki), callouts, FAQ schema, tables, task lists
@@ -44,9 +46,9 @@ npx nextblogkit init
44
46
  ```
45
47
 
46
48
  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)
49
+ - `app/blogs/` — Blog pages (list, post, category)
50
+ - `app/admin/blogs/` — Admin panel pages (dashboard, posts, media, categories, settings)
51
+ - `app/api/blogs/` — API routes (posts, media, categories, settings, sitemap, RSS)
50
52
  - `nextblogkit.config.ts` — Configuration file
51
53
  - `.env.local.example` — Environment variable template
52
54
 
@@ -100,8 +102,8 @@ This creates the required MongoDB indexes for posts, categories, and media.
100
102
  pnpm dev
101
103
  ```
102
104
 
103
- - Blog: [http://localhost:3000/blog](http://localhost:3000/blog)
104
- - Admin: [http://localhost:3000/admin/blog](http://localhost:3000/admin/blog)
105
+ - Blog: [http://localhost:3000/blogs](http://localhost:3000/blogs)
106
+ - Admin: [http://localhost:3000/admin/blogs](http://localhost:3000/admin/blogs)
105
107
 
106
108
  The admin panel will prompt you for the API key on first visit (the value of `NEXTBLOGKIT_API_KEY`).
107
109
 
@@ -116,9 +118,9 @@ import { defineConfig } from 'nextblogkit';
116
118
 
117
119
  export default defineConfig({
118
120
  // URL paths — customize to match your site structure
119
- basePath: '/blog', // Public blog URL prefix
120
- adminPath: '/admin/blog', // Admin panel URL prefix
121
- apiPath: '/api/blog', // API routes URL prefix
121
+ basePath: '/blogs', // Public blog URL prefix
122
+ adminPath: '/admin/blogs', // Admin panel URL prefix
123
+ apiPath: '/api/blogs', // API routes URL prefix
122
124
 
123
125
  // Pagination
124
126
  postsPerPage: 10,
@@ -163,7 +165,7 @@ export default defineConfig({
163
165
 
164
166
  ### Custom URL Paths
165
167
 
166
- 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:
168
+ You can change the blog URL from `/blogs` to anything — `/articles`, `/posts`, `/blog`, etc. Make sure the folder structure in your `app/` directory matches and pass the paths through to components:
167
169
 
168
170
  ```typescript
169
171
  export default defineConfig({
@@ -182,7 +184,7 @@ import 'nextblogkit/styles/admin.css';
182
184
 
183
185
  export default function AdminBlogLayout({ children }: { children: React.ReactNode }) {
184
186
  return (
185
- <AdminLayout apiPath="/api/articles" adminPath="/admin/articles">
187
+ <AdminLayout apiPath="/api/articles" adminPath="/admin/articles" basePath="/articles">
186
188
  {children}
187
189
  </AdminLayout>
188
190
  );
@@ -228,7 +230,7 @@ NextBlogKit uses CSS variables for theming. Every color, font, radius, and shado
228
230
  Add a `<style>` block in your blog layout or override in your global CSS:
229
231
 
230
232
  ```tsx
231
- // app/blog/layout.tsx
233
+ // app/blogs/layout.tsx
232
234
  import 'nextblogkit/styles/blog.css';
233
235
 
234
236
  export default function BlogLayout({ children }: { children: React.ReactNode }) {
@@ -340,7 +342,7 @@ import { BlogPostPage } from 'nextblogkit/components';
340
342
  | `showRelatedPosts` | `boolean` | `true` | Show related posts section |
341
343
  | `showShareButtons` | `boolean` | `true` | Show share buttons |
342
344
  | `showReadingProgress` | `boolean` | `true` | Show reading progress bar at top |
343
- | `basePath` | `string` | `'/blog'` | Blog URL prefix (for links) |
345
+ | `basePath` | `string` | `'/blogs'` | Blog URL prefix (for links) |
344
346
  | `className` | `string` | `''` | Additional CSS class |
345
347
  | `slots` | `BlogPostSlots` | — | Custom content injection (see below) |
346
348
 
@@ -384,7 +386,7 @@ interface BlogPostSlots {
384
386
  **Full page example:**
385
387
 
386
388
  ```tsx
387
- // app/blog/[slug]/page.tsx
389
+ // app/blogs/[slug]/page.tsx
388
390
  import { BlogPostPage } from 'nextblogkit/components';
389
391
  import { getPostBySlug, listPosts, generateMetaTags } from 'nextblogkit/lib';
390
392
  import type { Metadata } from 'next';
@@ -433,7 +435,7 @@ export default async function BlogPost({ params }: Props) {
433
435
  relatedPosts={JSON.parse(JSON.stringify(relatedPosts))}
434
436
  showTOC
435
437
  tocPosition="sidebar"
436
- basePath="/blog"
438
+ basePath="/blogs"
437
439
  />
438
440
  );
439
441
  }
@@ -462,8 +464,8 @@ import { BlogListPage } from 'nextblogkit/components';
462
464
  | `showCategories` | `boolean` | `true` | Show category sidebar |
463
465
  | `showSearch` | `boolean` | `true` | Show search bar |
464
466
  | `layout` | `'grid' \| 'list' \| 'magazine'` | `'grid'` | Post card layout style |
465
- | `basePath` | `string` | `'/blog'` | Blog URL prefix |
466
- | `apiPath` | `string` | `'/api/blog'` | API URL prefix (for search) |
467
+ | `basePath` | `string` | `'/blogs'` | Blog URL prefix |
468
+ | `apiPath` | `string` | `'/api/blogs'` | API URL prefix (for search) |
467
469
  | `className` | `string` | `''` | Additional CSS class |
468
470
  | `slots` | `BlogListSlots` | — | Custom content injection (see below) |
469
471
 
@@ -572,20 +574,20 @@ Wraps all admin pages with sidebar navigation and authentication.
572
574
  |------|------|---------|-------------|
573
575
  | `children` | `ReactNode` | *required* | Page content |
574
576
  | `apiKey` | `string` | — | Pre-set API key (bypasses login prompt) |
575
- | `apiPath` | `string` | `'/api/blog'` | API route prefix for all admin API calls |
576
- | `adminPath` | `string` | `'/admin/blog'` | Admin route prefix for sidebar nav links |
577
- | `basePath` | `string` | `'/blog'` | Public blog URL prefix (used for "View" links in post list and SEO preview) |
577
+ | `apiPath` | `string` | `'/api/blogs'` | API route prefix for all admin API calls |
578
+ | `adminPath` | `string` | `'/admin/blogs'` | Admin route prefix for sidebar nav links |
579
+ | `basePath` | `string` | `'/blogs'` | Public blog URL prefix (used for "View" links in post list and SEO preview) |
578
580
 
579
581
  **Important:** If you change `apiPath` or `basePath` in your config, you **must** pass them to `AdminLayout`:
580
582
 
581
583
  ```tsx
582
- // app/admin/blog/layout.tsx
584
+ // app/admin/blogs/layout.tsx
583
585
  import { AdminLayout } from 'nextblogkit/admin';
584
586
  import 'nextblogkit/styles/admin.css';
585
587
 
586
588
  export default function AdminBlogLayout({ children }: { children: React.ReactNode }) {
587
589
  return (
588
- <AdminLayout apiPath="/api/blogs" adminPath="/admin/blog" basePath="/blogs">
590
+ <AdminLayout apiPath="/api/blogs" adminPath="/admin/blogs" basePath="/blogs">
589
591
  {children}
590
592
  </AdminLayout>
591
593
  );
@@ -599,10 +601,10 @@ export default function AdminBlogLayout({ children }: { children: React.ReactNod
599
601
  Import the CSS files you need in your layout files:
600
602
 
601
603
  ```tsx
602
- // Blog pages — import in app/blog/layout.tsx
604
+ // Blog pages — import in app/blogs/layout.tsx
603
605
  import 'nextblogkit/styles/blog.css';
604
606
 
605
- // Admin pages — import in app/admin/blog/layout.tsx
607
+ // Admin pages — import in app/admin/blogs/layout.tsx
606
608
  import 'nextblogkit/styles/admin.css';
607
609
  ```
608
610
 
@@ -705,53 +707,53 @@ All API routes require a Bearer token (`NEXTBLOGKIT_API_KEY`) for write operatio
705
707
 
706
708
  | Method | Endpoint | Description |
707
709
  |--------|----------|-------------|
708
- | GET | `/api/blog/posts` | List posts (supports `?status=`, `?category=`, `?search=`, `?page=`, `?limit=`) |
709
- | GET | `/api/blog/posts?slug=my-post` | Get single post by slug |
710
- | GET | `/api/blog/posts?id=abc123` | Get single post by ID |
711
- | POST | `/api/blog/posts` | Create post |
712
- | PUT | `/api/blog/posts` | Update post (requires `?id=`) |
713
- | DELETE | `/api/blog/posts` | Delete/archive post (requires `?id=`) |
710
+ | GET | `/api/blogs/posts` | List posts (supports `?status=`, `?category=`, `?search=`, `?page=`, `?limit=`) |
711
+ | GET | `/api/blogs/posts?slug=my-post` | Get single post by slug |
712
+ | GET | `/api/blogs/posts?id=abc123` | Get single post by ID |
713
+ | POST | `/api/blogs/posts` | Create post |
714
+ | PUT | `/api/blogs/posts` | Update post (requires `?id=`) |
715
+ | DELETE | `/api/blogs/posts` | Delete/archive post (requires `?id=`) |
714
716
 
715
717
  ### Media
716
718
 
717
719
  | Method | Endpoint | Description |
718
720
  |--------|----------|-------------|
719
- | GET | `/api/blog/media` | List media files |
720
- | POST | `/api/blog/media` | Upload file (multipart/form-data) |
721
- | DELETE | `/api/blog/media?id=abc123` | Delete media file |
721
+ | GET | `/api/blogs/media` | List media files |
722
+ | POST | `/api/blogs/media` | Upload file (multipart/form-data) |
723
+ | DELETE | `/api/blogs/media?id=abc123` | Delete media file |
722
724
 
723
725
  ### Categories
724
726
 
725
727
  | Method | Endpoint | Description |
726
728
  |--------|----------|-------------|
727
- | GET | `/api/blog/categories` | List categories |
728
- | POST | `/api/blog/categories` | Create category |
729
- | PUT | `/api/blog/categories?id=abc123` | Update category |
730
- | DELETE | `/api/blog/categories?id=abc123` | Delete category |
729
+ | GET | `/api/blogs/categories` | List categories |
730
+ | POST | `/api/blogs/categories` | Create category |
731
+ | PUT | `/api/blogs/categories?id=abc123` | Update category |
732
+ | DELETE | `/api/blogs/categories?id=abc123` | Delete category |
731
733
 
732
734
  ### Settings
733
735
 
734
736
  | Method | Endpoint | Description |
735
737
  |--------|----------|-------------|
736
- | GET | `/api/blog/settings` | Get settings |
737
- | PUT | `/api/blog/settings` | Update settings |
738
+ | GET | `/api/blogs/settings` | Get settings |
739
+ | PUT | `/api/blogs/settings` | Update settings |
738
740
 
739
- > **Note:** These endpoints use the default `/api/blog` prefix. If you changed `apiPath` in your config, replace `/api/blog` with your custom path.
741
+ > **Note:** These endpoints use the default `/api/blogs` prefix. If you changed `apiPath` in your config, replace `/api/blogs` with your custom path.
740
742
 
741
743
  ### Tokens
742
744
 
743
745
  | Method | Endpoint | Description |
744
746
  |--------|----------|-------------|
745
- | GET | `/api/blog/tokens` | List API tokens (master key only) |
746
- | POST | `/api/blog/tokens` | Generate new token (master key only) |
747
- | DELETE | `/api/blog/tokens?id=abc123` | Revoke token (master key only) |
747
+ | GET | `/api/blogs/tokens` | List API tokens (master key only) |
748
+ | POST | `/api/blogs/tokens` | Generate new token (master key only) |
749
+ | DELETE | `/api/blogs/tokens?id=abc123` | Revoke token (master key only) |
748
750
 
749
751
  ### Authentication
750
752
 
751
753
  Include the API key or a generated token as a Bearer token:
752
754
 
753
755
  ```bash
754
- curl -X POST http://localhost:3000/api/blog/posts \
756
+ curl -X POST http://localhost:3000/api/blogs/posts \
755
757
  -H "Authorization: Bearer your-api-key-here" \
756
758
  -H "Content-Type: application/json" \
757
759
  -d '{"title": "My Post", "content": [...], "status": "published"}'
@@ -836,7 +838,7 @@ function MyEditor() {
836
838
  const formData = new FormData();
837
839
  formData.append('file', file);
838
840
 
839
- const res = await fetch('/api/blog/media', {
841
+ const res = await fetch('/api/blogs/media', {
840
842
  method: 'POST',
841
843
  headers: { Authorization: `Bearer ${apiKey}` },
842
844
  body: formData,
@@ -866,8 +868,8 @@ NextBlogKit automatically generates:
866
868
  - **Open Graph** — og:title, og:description, og:image, og:type
867
869
  - **Twitter Cards** — twitter:card, twitter:title, twitter:description
868
870
  - **JSON-LD** — BlogPosting and FAQPage structured data
869
- - **Sitemap** — Dynamic XML sitemap at `/api/blog/sitemap.xml`
870
- - **RSS** — RSS 2.0 feed at `/api/blog/rss.xml`
871
+ - **Sitemap** — Dynamic XML sitemap at `/api/blogs/sitemap.xml`
872
+ - **RSS** — RSS 2.0 feed at `/api/blogs/rss.xml`
871
873
 
872
874
  The built-in SEO scorer checks 17 factors including keyword density, title length, heading hierarchy, image alt text, and readability.
873
875
 
@@ -896,11 +898,11 @@ Running `npx nextblogkit init` creates thin wrapper files inside your `app/` dir
896
898
 
897
899
  ```
898
900
  app/
899
- ├── blog/
901
+ ├── blogs/
900
902
  │ ├── page.tsx # Blog list page
901
903
  │ ├── [slug]/page.tsx # Individual post page (with SEO metadata)
902
904
  │ └── category/[slug]/page.tsx # Category filter page
903
- ├── admin/blog/
905
+ ├── admin/blogs/
904
906
  │ ├── layout.tsx # Admin layout with sidebar
905
907
  │ ├── page.tsx # Dashboard
906
908
  │ ├── posts/page.tsx # Post list
@@ -909,7 +911,7 @@ app/
909
911
  │ ├── media/page.tsx # Media library
910
912
  │ ├── categories/page.tsx # Category manager
911
913
  │ └── settings/page.tsx # Settings
912
- └── api/blog/
914
+ └── api/blogs/
913
915
  ├── posts/route.ts # Posts CRUD
914
916
  ├── media/route.ts # Media upload/list/delete
915
917
  ├── categories/route.ts # Categories CRUD
@@ -1009,6 +1011,22 @@ pnpm dev
1009
1011
  | Build | tsup (ESM + CJS) |
1010
1012
  | Styling | CSS Variables + Tailwind-compatible |
1011
1013
 
1014
+ ## ButterBlogs Integration
1015
+
1016
+ NextBlogKit is built and sponsored by [ButterBlogs](https://butterblogs.com) — an AI content platform that automates blog creation from research to publication.
1017
+
1018
+ **How they work together:**
1019
+
1020
+ 1. **Write** — Use ButterBlogs to research topics, generate SEO-optimized articles with your brand voice, and export as structured content
1021
+ 2. **Publish** — Push directly to your NextBlogKit-powered blog via the API, or paste into the admin editor
1022
+ 3. **Rank** — NextBlogKit handles the technical SEO (JSON-LD schema, sitemap, RSS, meta tags) while ButterBlogs handles the content SEO (keyword targeting, EEAT signals, internal linking)
1023
+
1024
+ This gives you a complete content pipeline: AI-assisted writing with a self-hosted, fully customizable blog frontend — no third-party CMS lock-in.
1025
+
1026
+ [Get started with ButterBlogs](https://butterblogs.com)
1027
+
1028
+ ---
1029
+
1012
1030
  ## License
1013
1031
 
1014
1032
  MIT
@@ -123,22 +123,56 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
123
123
  setBasePath(basePath);
124
124
  }
125
125
  }, [apiPath, basePath]);
126
+ const [initializing, setInitializing] = React4.useState(true);
126
127
  React4.useEffect(() => {
127
- if (typeof window !== "undefined") {
128
- setCurrentPath(window.location.pathname);
129
- const stored = sessionStorage.getItem("nbk_api_key");
130
- if (stored || apiKey) {
128
+ if (typeof window === "undefined") return;
129
+ setCurrentPath(window.location.pathname);
130
+ const stored = sessionStorage.getItem("nbk_api_key");
131
+ const key = stored || apiKey;
132
+ if (!key) {
133
+ setInitializing(false);
134
+ return;
135
+ }
136
+ const base = apiPath || "/api/blog";
137
+ fetch(`${base}/settings`, {
138
+ headers: { Authorization: `Bearer ${key}` }
139
+ }).then((res) => {
140
+ if (res.ok) {
131
141
  setIsAuthenticated(true);
142
+ } else {
143
+ sessionStorage.removeItem("nbk_api_key");
132
144
  }
133
- }
134
- }, [apiKey]);
135
- const handleLogin = (e) => {
145
+ }).catch(() => {
146
+ if (key) setIsAuthenticated(true);
147
+ }).finally(() => setInitializing(false));
148
+ }, [apiKey, apiPath]);
149
+ const [loginError, setLoginError] = React4.useState("");
150
+ const [loginLoading, setLoginLoading] = React4.useState(false);
151
+ const handleLogin = async (e) => {
136
152
  e.preventDefault();
137
- if (inputKey.trim()) {
138
- sessionStorage.setItem("nbk_api_key", inputKey);
139
- setIsAuthenticated(true);
153
+ if (!inputKey.trim()) return;
154
+ setLoginError("");
155
+ setLoginLoading(true);
156
+ try {
157
+ const base = apiPath || "/api/blog";
158
+ const res = await fetch(`${base}/settings`, {
159
+ headers: { Authorization: `Bearer ${inputKey}` }
160
+ });
161
+ if (res.ok) {
162
+ sessionStorage.setItem("nbk_api_key", inputKey);
163
+ setIsAuthenticated(true);
164
+ } else {
165
+ setLoginError("Invalid API key");
166
+ }
167
+ } catch {
168
+ setLoginError("Unable to connect to server");
169
+ } finally {
170
+ setLoginLoading(false);
140
171
  }
141
172
  };
173
+ if (initializing) {
174
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-admin-login", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-login-card", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-login-subtitle", children: "Verifying..." }) }) });
175
+ }
142
176
  if (!isAuthenticated) {
143
177
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nbk-admin-login", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nbk-login-card", children: [
144
178
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "nbk-login-title", children: "Blog Admin" }),
@@ -152,10 +186,12 @@ function AdminLayout({ children, apiKey, apiPath, adminPath = "/admin/blog", bas
152
186
  onChange: (e) => setInputKey(e.target.value),
153
187
  placeholder: "Enter API key",
154
188
  className: "nbk-login-input",
155
- autoFocus: true
189
+ autoFocus: true,
190
+ disabled: loginLoading
156
191
  }
157
192
  ),
158
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "nbk-login-btn", children: "Sign In" })
193
+ loginError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "nbk-login-error", children: loginError }),
194
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "nbk-login-btn", disabled: loginLoading, children: loginLoading ? "Verifying..." : "Sign In" })
159
195
  ] })
160
196
  ] }) });
161
197
  }