Inkwell is a full-stack blog platform built end-to-end on the Next.js 15 App Router. It pairs a polished writing experience with everything a modern reader expects — and wraps it in a fast, accessible, dark-mode-ready UI.
No separate REST or GraphQL API. Every data operation is a React Server Component or Server Action — type-safe from database to browser.
🔐 Authentication & Accounts
- Email + password sign-up and login with bcrypt-hashed credentials
- Google OAuth one-click sign-in via Auth.js v5
- Role-based access control —
USER/ADMINwith middleware + server-side enforcement - Full profile management — name, bio, avatar, password change
- JWT sessions — stateless, edge-compatible
- Rate limiting on registration, commenting, liking, and AI usage
🛡️ Admin Dashboard
- Protected
/adminarea — role-checked at the middleware and server level - Platform overview: total users, posts, comments, likes
- User management — promote/demote roles, remove accounts
- Post moderation — unpublish or delete any post
- Comment moderation — remove any comment
✍️ Writing & Publishing
- Rich-text editor (TipTap) — headings, bullet & ordered lists, links, inline images
- AI writing assistant (OpenRouter, free-tier fallback chain):
- ✦ Improve a selected passage in-place
- ✦ Generate 5 title suggestions from your draft
- ✦ Write a summary automatically
- ✦ Suggest relevant tags
- Cover images & in-editor image uploads via UploadThing
- Chip-style tag input — tags are auto-created and reused across posts
- Draft → Publish workflow — toggle live from the editor footer
- Auto slug generation and reading-time estimate on save
- Author- and admin-scoped edit / delete authorization
📚 Reading & Discovery
- Infinite-scroll feed with cursor-based pagination — no page refreshes
- Full-text search across titles and summaries
- Tag filter pages (
/blog/tag/[slug]) for topic browsing - Related stories — same-tag / same-author suggestions on every post
- Auto-generated table of contents from post headings
- Reading-progress bar that tracks scroll position
- Public author profiles (
/author/[id]) with bio and published work
💬 Engagement & Social
- Likes and bookmarks with optimistic UI (no spinner, instant feedback)
- Personal Bookmarks page to save stories for later
- Threaded comments with nested replies
- Inline comment editing with an "edited" marker
- Follow authors and a personalised Following feed
- In-app notifications — bell dropdown for likes, comments, replies, and follows
- View counter per post, tracked with a time-series event model
- Share buttons — X (Twitter), LinkedIn, and copy-link
📊 Analytics & SEO
- Author analytics dashboard — 30-day views, likes, and comments charted with Recharts
- Timestamped view tracking (
PostViewevents) powering the time-series; a denormalisedviewCountcounter keeps headline numbers fast - Top posts table with per-post engagement breakdown
- Dynamic
sitemap.xml— static pages, published posts, tags, and authors; revalidates hourly robots.txtthat keeps/dashboard,/admin, and/api/out of the index- RSS feed at
/feed.xml— the 30 most recent posts; advertised via<link rel="alternate">
🎨 Experience & Accessibility
- Light / Dark / System theme switching (next-themes)
- Fully responsive across mobile, tablet, and desktop
- Accessible UI built on shadcn/ui (Base UI primitives)
- Toast notifications (Sonner) for every action
- Graceful loading, error, and not-found states throughout
- Per-page SEO metadata, Open Graph, and Twitter card tags
| Layer | Technology |
|---|---|
| Framework | Next.js 15 — App Router, RSC, Server Actions, Middleware |
| Language | TypeScript 5 · React 19 |
| Database | PostgreSQL · Prisma 7 (driver adapter) |
| Auth | Auth.js v5 / NextAuth — Credentials + Google OAuth |
| Styling | Tailwind CSS v4 · shadcn/ui (Base UI) |
| Editor | TipTap |
| AI | OpenRouter — free model fallback chain (gpt-oss-120b → gpt-oss-20b → llama-3.3-70b) |
| Charts | Recharts |
| Uploads | UploadThing |
| Validation | Zod v4 |
| Toasts | Sonner |
| Rate limiting | In-memory sliding-window (per-route, per-user) |
git clone https://github.com/<your-username>/inkwell.git
cd inkwell
npm installcp .env.example .envOpen .env and fill in every value — see the table below:
| Variable | Where to get it |
|---|---|
DATABASE_URL |
Your PostgreSQL connection string |
NEXTAUTH_URL |
http://localhost:3000 in dev; your domain in prod |
NEXTAUTH_SECRET |
openssl rand -base64 32 |
AUTH_GOOGLE_ID |
Google Cloud Console |
AUTH_GOOGLE_SECRET |
Google Cloud Console |
UPLOADTHING_TOKEN |
UploadThing dashboard |
OPENROUTER_API_KEY |
openrouter.ai/keys (optional — AI features hide when unset) |
npm run db:migrate # apply all migrations
npm run db:seed # seed 10 demo users + 30 postsnpm run devOpen http://localhost:3000 🎉
After seeding you can sign in with any of the 10 demo accounts. Two to start with:
| Password | Role | |
|---|---|---|
alice@inkwell.dev |
password123 |
Admin |
bob@inkwell.dev |
password123 |
User |
All 10 accounts share the password password123. Emails follow the pattern <name>@inkwell.dev — alice, bob, carol, david, emma, farah, george, hana, ivan, julia.
| Script | Description |
|---|---|
npm run dev |
Start the development server (with Turbopack) |
npm run build |
Create an optimised production build |
npm run start |
Run the production server |
npm run lint |
Lint with ESLint |
npm run db:migrate |
Create & apply a new migration |
npm run db:seed |
Seed the database with demo data |
npm run db:studio |
Open Prisma Studio (visual DB browser) |
npm run db:reset |
Drop, re-migrate, and re-seed (destructive) |
inkwell/
├── prisma/
│ ├── schema.prisma # Full data model (User, Post, Tag, Comment, Like, …)
│ ├── migrations/ # Versioned SQL migration history
│ └── seed.ts # 10 users · 30 posts · follows · bookmarks · analytics
│
├── src/
│ ├── actions/ # Server Actions
│ │ ├── posts.ts # CRUD, view tracking (dual-write)
│ │ ├── comments.ts # Create, edit, delete (threaded)
│ │ ├── engagement.ts # Likes, bookmarks, follows
│ │ ├── profile.ts # Avatar, bio, password change
│ │ └── ai.ts # AI generation actions (rate-limited)
│ │
│ ├── app/
│ │ ├── (auth)/ # /login, /register (route group)
│ │ ├── admin/ # Admin dashboard — users, posts, comments
│ │ ├── api/ # Auth.js & UploadThing route handlers
│ │ ├── author/[id]/ # Public author profiles
│ │ ├── blog/ # Feed, post detail, search, tag pages
│ │ ├── dashboard/ # Create/edit, bookmarks, profile, analytics
│ │ ├── feed.xml/ # RSS 2.0 route handler
│ │ ├── sitemap.ts # Dynamic sitemap.xml
│ │ └── robots.ts # robots.txt
│ │
│ ├── components/
│ │ ├── auth/ # Login & register forms
│ │ ├── blog/ # PostCard, InfiniteFeed, CommentSection, …
│ │ ├── dashboard/ # DashboardHeader, AnalyticsChart
│ │ ├── editor/ # RichTextEditor, PostEditorForm, AIAssist, TagInput
│ │ ├── layout/ # Navbar, Footer, ThemeToggle
│ │ └── ui/ # shadcn/ui primitives (Button, Card, Input, …)
│ │
│ ├── lib/
│ │ ├── prisma.ts # Singleton Prisma client (driver adapter)
│ │ ├── auth.ts # Auth.js session helpers
│ │ ├── analytics.ts # getAuthorAnalytics — time-series aggregation
│ │ ├── openrouter.ts # OpenRouter client + fallback chain
│ │ ├── rate-limit.ts # Sliding-window rate limiter
│ │ ├── site.ts # SITE_NAME, getSiteUrl()
│ │ ├── utils/ # cn(), extractPlainText(), content helpers
│ │ └── validations/ # Zod schemas (posts, auth, ai, …)
│ │
│ ├── types/ # Shared TypeScript types & interfaces
│ ├── auth.ts # Auth.js v5 configuration
│ └── middleware.ts # Route protection + role gating
1. Neon — free PostgreSQL
- Create a free project at neon.tech
- Copy the connection string — it looks like:
postgresql://user:pass@ep-xxx.neon.tech/neondb?sslmode=require
2. Vercel — free hosting
- Push this repo to GitHub
- Import the repo at vercel.com/new
- Add every variable from
.env.examplein Project → Settings → Environment Variables - Set
NEXTAUTH_URLto your assigned Vercel domain (e.g.https://inkwell.vercel.app) - Deploy — Prisma generates the client automatically via
postinstall
3. Migrate & seed production
Run once against your Neon database:
DATABASE_URL="<neon-url>" npx prisma migrate deploy
DATABASE_URL="<neon-url>" npx prisma db seedAny Node-compatible host works too — Railway, Render, Fly.io — just provision PostgreSQL and set the same environment variables.
Google OAuth in production
In Google Cloud Console add your Vercel domain to:
- Authorised JavaScript origins —
https://your-app.vercel.app - Authorised redirect URIs —
https://your-app.vercel.app/api/auth/callback/google
| Status | Feature |
|---|---|
| 🔲 | Password reset & email verification via Resend |
| 🔲 | Multi-instance rate limiting & AI quotas via Upstash Redis |
| 🔲 | Real-time notifications (WebSockets / SSE) |
| 🔲 | Post scheduling — set a future publish date |
| 🔲 | Newsletter / email digest for followers |
Released under the MIT License — free to use, learn from, and build upon.
Built with ❤️ using Next.js 15, Prisma 7, and TypeScript.