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.