@pushstorm/next-js-blog-management
v1.1.2
Published
Drop-in blog management for Next.js
Downloads
36
Readme
Features
Admin Dashboard
- Create, edit, and delete blog posts
- Rich text editor with bold, italic, headings, lists, links, images, code blocks
- Image upload with drag and drop (cover image + OG image)
- Draft / Published status
- Filter by status, search, sort by date or title
- Grid and list view toggle
- Pagination
SEO
- Meta title and description (separate from post title/excerpt)
- Focus keyword with live analysis (checks title, slug, meta description, body)
- Open Graph fields (title, description, image)
- Canonical URL
- No-index toggle
- Schema.org type (BlogPosting, NewsArticle, Article)
- JSON-LD structured data on every post
generateMetadataper post for Next.js App Router- Auto sitemap at
/sitemap.xml
Public Blog
- Blog listing page with sidebar (categories, recent posts, tags)
- Search, filter by category, sort
- Client-side pagination
- Featured post hero section
- Blog carousel
- Full post page with related posts carousel (shadcn)
- Reading time
- Optimised images via
next/image
API Reference
All endpoints are at /api/blog.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/blog | List posts — supports page, limit, sort, status, search |
| POST | /api/blog | Create a post (multipart/form-data) |
| GET | /api/blog/[id] | Get a single post by ID |
| PATCH | /api/blog/[id] | Update a post (multipart/form-data) |
| DELETE | /api/blog/[id] | Delete a post and its images |
| GET | /api/blog/slug/[slug] | Get a published post by slug |
GET /api/blog query params:
Add ThemeProvider, TooltipProvider and Script Tag to supress the hydration error
<head>
<Script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var saved = localStorage.getItem('theme');
if (saved === 'dark' || saved === 'light') {
if (saved === 'dark') document.documentElement.classList.add('dark');
} else {
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) document.documentElement.classList.add('dark');
}
} catch(e) {}
})();
`,
}}
/>
</head>
<body className="min-h-full ">
<ThemeModeProvider>
<TooltipProvider>{children}</TooltipProvider>
</ThemeModeProvider>
</body>Add the styling for proper functioning of the Dashboard
@import "tailwindcss";
@plugin "@tailwindcss/typography";
:root {
/* Base */
--background: #fafbf9;
--surface: #ffffff;
--foreground: #0f172a;
--foreground-muted: #64748b;
--destructive: #ff0040;
/* Accent */
--primary: #0ea5e9;
--brand: #0ea5e9;
--brand-subtle: #e0f2fe;
/* Borders */
--border-color: #dde1e7;
/* Sidebar */
--sb-item-active: #e0f2fe;
--sb-item-hover: #f1f5f9;
--sb-text-primary: #0f172a;
--sb-text-secondary: #475569;
--sb-text-muted: #94a3b8;
--sb-border: #dde1e7;
}
.dark {
/* Base */
--background: #0d1117;
--surface: #161b22;
--foreground: #e6edf3;
--foreground-muted: #8b949e;
/* Accent */
--primary: #38bdf8;
--brand: #38bdf8;
--brand-subtle: #0c2d3f;
/* Borders */
--border-color: #21262d;
--destructive: #ff0040;
/* Sidebar */
--sb-item-active: #0c2d3f;
--sb-item-hover: #1c2128;
--sb-text-primary: #e6edf3;
--sb-text-secondary: #8b949e;
--sb-text-muted: #484f58;
--sb-border: #21262d;
}
@theme inline {
--color-background: var(--background);
--color-surface: var(--surface);
--color-foreground: var(--foreground);
--color-foreground-muted: var(--foreground-muted);
--color-primary: var(--primary);
--color-brand: var(--brand);
--color-brand-subtle: var(--brand-subtle);
--color-border-color: var(--border-color);
--color-border: var(--border-color);
--color-sb-item-active: var(--sb-item-active);
--color-sb-item-hover: var(--sb-item-hover);
--color-sb-text-primary: var(--sb-text-primary);
--color-sb-text-secondary: var(--sb-text-secondary);
--color-sb-text-muted: var(--sb-text-muted);
--color-sb-border: var(--sb-border);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-destructive: var(--destructive);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
transition: background-color 0.2s ease-in-out;
}
@layer components {
.input {
@apply h-12;
}
.button {
@apply h-12;
}
}
.rte-editor .tiptap {
@apply h-60 overflow-y-auto p-4 outline-none text-foreground/80;
}
.rte-editor .tiptap a {
@apply text-blue-500 underline cursor-pointer;
}
.rte-editor .tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
color: var(--foreground-muted);
pointer-events: none;
float: left;
height: 0;
}
.rte-editor .tiptap h1 {
@apply text-3xl font-bold mb-3;
}
.rte-editor .tiptap h2 {
@apply text-2xl font-semibold mb-2;
}
.rte-editor .tiptap h3 {
@apply text-xl font-semibold mb-2;
}
.rte-editor .tiptap ul {
@apply list-disc ml-6;
}
.rte-editor .tiptap ol {
@apply list-decimal ml-6;
}
.rte-editor .tiptap blockquote {
@apply border-l-4 border-gray-600 pl-4 italic text-foreground;
}
.rte-editor .tiptap pre {
@apply bg-gray-900 text-gray-100 rounded-md p-3 overflow-x-auto;
}
.rte-editor .tiptap code {
@apply bg-gray-800 text-pink-400 px-1 py-0.5 rounded;
}