Smart Story Book Journal - Next.js, TypeScript, PostgreSQL, Tailwind CSS (Animated Book UI) FullStack Project (Online-Offline Draft, Sync Capability, Voice Transcription, AI Assist Daily Diary)
A premium, immersive digital diary that feels like opening a real leather notebook. It combines 3D book-cover animations, CSS page-flip transitions, ruled paper, mood and weather pickers, tags, autosave, IndexedDB offline drafts, multi-tab SSE realtime sync, and optional AI writing assistance — all backed by PostgreSQL, Prisma, and NextAuth secure authentication. This repository is built for learning: you can study how a modern full-stack Next.js app splits Server Components (data) from Client Components (animation), how REST APIs validate with Zod, how TanStack Query keeps the UI fresh without full page reloads, and how offline-first patterns survive flaky networks.
- Live Demo: https://storybook-journal.vercel.app
- What Is This Project?
- Key Features
- Technology Stack
- How It Works (Architecture)
- Project Structure
- Routes & Pages
- API Endpoints
- Database Schema
- Environment Variables
- Getting Started
- Available Scripts
- Learning Walkthrough
- In-Source Code Comments
- Reusable Components & Patterns
- Code Examples
- Keywords & SEO
- Related Documentation
- Conclusion
- License
StoryBook Journal is a full-stack journaling web application — not a minimal CRUD tutorial. The product goal is emotional and tactile:
- Open a leather book cover with a 3D hinge animation on the landing page
- Flip pages between login and register inside an open-book auth spread
- Browse multiple journals on a bookshelf dashboard
- Read and write daily entries on a two-page spread (left = entry list, right = read/write)
- Autosave while typing; survive refreshes with offline IndexedDB drafts
- Sync queued creates/updates when the network returns
- Search entries from a ⌘K command palette
- Cycle page themes (warm paper, dark academia, midnight journal, and more)
- Get AI writing suggestions through a secure server proxy (optional)
- Inspect API health and live API documentation inside the authenticated dashboard
| Feature | Description |
|---|---|
| 3D book cover | Leather texture, gold shine, hover tilt, animated cover open on landing |
| Page-flip navigation | CSS preserve-3d flip overlay; auth and journal routes animate before content swap |
| Book spread UI | Left page = previous entry preview + TOC; right page = read or write mode |
| Multiple journals | Create books with cover color, Lucide icon slug, and page theme |
| Rich entry metadata | Mood, weather, location, tags, word count, reading time |
| TipTap editor | Rich HTML content with placeholder, typography, character count |
| Autosave | Debounced PATCH while editing (2 s default via useAutoSave) |
| Offline-first | IndexedDB entry drafts + sync queue for PATCH/POST when offline |
| Optimistic UI | TanStack Query cache updates instantly; server reconciles on sync |
| Realtime SSE | GET /api/journal/events — multi-tab invalidation via Redis pub/sub |
| Authentication | Email/password (bcrypt) + optional Google OAuth (NextAuth v5) |
| Demo login | Pre-filled test account dropdown on /login for quick exploration |
| AI writing assist | Groq → OpenRouter → Anthropic fallback; keys never exposed to browser |
| Voice dictation | Mic button in write mode — Web Speech (free) → browser Whisper (private) → cloud STT |
| API Status UI | /api-status — dependency health (DB, Redis, AI) + aggregate stats |
| API Docs UI | /api-documentation — OpenAPI-style catalog from api-route-catalog.ts |
| Security | proxy.ts auth gate, security headers, dashboard noindex, Zod validation |
| Responsive book | CSS clamp() tokens (--page-w, --page-h); mobile horizontal scroll |
Each library plays a specific role. Understanding why it is here helps you reuse patterns in other projects.
| Library | Version | Role |
|---|---|---|
| Next.js | 16 | App Router, Server Components, Route Handlers, metadata API |
| React | 19 | UI rendering, hooks, client/server component split |
| TypeScript | 5.7 | Strict typing across API, DB, and UI |
Next.js App Router means files under src/app/ map to URLs. page.tsx renders routes; layout.tsx wraps nested routes; route.ts inside api/ becomes REST endpoints.
| Library | Role |
|---|---|
| Prisma | ORM — type-safe PostgreSQL queries, migrations, schema |
| PostgreSQL | Relational database for users, books, entries |
| Zod | Runtime validation shared between API routes and forms |
| bcryptjs | Password hashing before storing in User.passwordHash |
| Library | Role |
|---|---|
| NextAuth v5 | JWT sessions, Credentials + Google providers, auth() helper |
Sessions use HttpOnly cookies. The Prisma user id is copied into the JWT so API routes scope every query with session.user.id.
| Library | Role |
|---|---|
| TanStack Query | Server-state cache, prefetch, invalidation after CRUD |
| React Hook Form | Login/register form state |
| Sonner | Toast notifications (save, offline queue, errors) |
| TipTap | Rich text editor (StarterKit, Placeholder, Typography) |
| cmdk | ⌘K command palette keyboard navigation |
| Radix UI | Dialog, dropdown, tabs, tooltip — accessible primitives |
| date-fns | Format entry dates (MMMM d, yyyy, weekday names) |
| lucide-react | Icon set for nav, shelf spines, and UI chrome |
| Framer Motion | Available for motion (book UI uses mostly custom CSS) |
| Library | Role |
|---|---|
| Upstash Redis | AI rate limiting + journal pub/sub for SSE realtime |
| Vitest | Unit tests (96+ tests) |
| Playwright | E2E tests (optional; requires seed data) |
| Library | Role |
|---|---|
| Tailwind CSS | Utility classes for forms, nav, modals |
| globals.css | Book aesthetic: leather, paper, flip keyframes, design tokens |
| Self-hosted fonts | 15 WOFF2 files in public/fonts/ — no Google Fonts CDN |
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ ┌──────────────┐
│ Browser │────▶│ Next.js (App) │────▶│ Prisma │────▶│ PostgreSQL │
│ React UI │◀────│ Server + API │◀────│ Client │◀────│ │
└─────────────┘ └──────────────────┘ └─────────────┘ └──────────────┘
│ │ │
│ IndexedDB │ NextAuth JWT │ Upstash Redis (optional)
▼ ▼ ▼
Offline drafts Session cookies SSE + rate limits
Sync queue- User opens
/journal/[bookId]. src/proxy.ts(Next.js 16 edge boundary) checks session; redirects to/loginif missing.journal/[bookId]/page.tsx(Server Component) callsauth()+ Prisma with ownership check.- Data passes as
initialBooktoBookSpread(Client Component). - User flips pages →
usePageFlipanimates → focused entry index changes locally. - URL updates via
history.replaceState(?entry=persists focus on refresh — no App Router round-trip per flip). - User edits →
useAutoSavedebounces →PATCH /api/entries/[entryId]. - On success →
notifyJournalCacheUpdated(queryClient)refreshes shelf + open book everywhere (no manual page refresh).
| Layer | Pattern |
|---|---|
| Server Components | Data fetch in page.tsx — Prisma never ships to the client bundle |
| Client Components | "use client" for flip, write mode, forms, offline hooks |
| API Route Handlers | src/app/api/**/route.ts — JSON REST, Zod validation, auth() guard |
| force-dynamic | Auth, dashboard, journal pages, and SSE/search APIs skip stale static cache |
Never call raw invalidateQueries({ queryKey: queryKeys.journalSubtree() }) outside src/lib/journal-cache-notify.ts. Always use:
import { notifyJournalCacheUpdated } from "@/lib/journal-cache-notify";
await notifyJournalCacheUpdated(queryClient);
// or when UI needs fresh bookDetail ids immediately:
await notifyJournalCacheUpdatedAndRefetch(queryClient, refetch);This single entry point invalidates shelf, book detail, search, API status counts, and SSE-driven updates consistently.
When the browser is offline (or fetch fails):
useOfflineEntryDraftsaves draft text to IndexedDB.offline-journal-actionsenqueues PATCH/POST with temp ids (offline-entry-*,offline-book-*).useOfflineSyncQueuedrains the queue onwindow.online.useOfflineIdRemapswaps temp ids to server cuids so the reader stays on the correct page.DashboardNavshows an{n} offlinebadge viaOfflineSyncContext.
- Server writes call
afterJournalMutation()→ Redis publish. - Client mounts
JournalRealtimeBridge→useJournalRealtimeopensEventSourceto/api/journal/events. - On event →
notifyJournalCacheUpdated— other tabs/devices see fresh data.
storybook-journal/
├── prisma/
│ ├── schema.prisma # User, JournalBook, JournalEntry models
│ ├── seed.ts # Optional demo data script
│ └── migrations/ # SQL migration history
├── public/
│ ├── fonts/ # Self-hosted WOFF2 (Playfair, Lora, Dancing Script, …)
│ └── diary-1.svg # App icon / brand mark
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # Root layout + SEO metadata
│ │ ├── page.tsx # Landing (3D cover) → redirect if logged in
│ │ ├── providers.tsx # SessionProvider, QueryClient, OfflineSync
│ │ ├── sitemap.ts # Public routes for SEO
│ │ ├── manifest.ts # PWA manifest
│ │ ├── robots.ts # Crawler rules
│ │ ├── (auth)/ # Login & register (book-spread auth UI)
│ │ ├── (dashboard)/ # Protected shelf, journal, API status/docs
│ │ └── api/ # REST Route Handlers
│ ├── components/
│ │ ├── auth/ # AuthBookShell, OAuth, demo picker
│ │ ├── journal/ # BookSpread, BookShelf, PageFlip, nav
│ │ ├── api-status/ # Live API health dashboard
│ │ ├── api-documentation/ # In-app API docs UI
│ │ ├── editor/ # TipTap JournalEditor
│ │ ├── forms/ # LoginForm, RegisterForm
│ │ ├── layout/ # DashboardNav, CommandProvider
│ │ ├── feedback/ # ConfirmDialog
│ │ └── ui/ # shadcn-style primitives (dialog, ripple-button, …)
│ ├── context/
│ │ └── OfflineSyncContext.tsx
│ ├── hooks/ # usePageFlip, useAutoSave, offline, realtime
│ ├── lib/ # auth, db, validations, journal-api, offline, AI
│ ├── constants/ # MOODS, WEATHERS, themes, cover icons
│ ├── types/ # Shared TS interfaces
│ └── proxy.ts # Auth redirect boundary (Next.js 16+)
├── docs/ # Extended guides (walkthrough, auth UI, guardrails)
├── .env.example # Template for all environment variables
├── docker-compose.yml # Optional local Postgres only (not the app)
├── next.config.ts # Security headers, image remotePatterns
├── vercel.json # Vercel header overrides
└── package.json| Route | Type | Description |
|---|---|---|
/ |
Server | Landing cover animation; redirects to /dashboard if authenticated |
/login |
Server | Login form inside book spread; optional Google OAuth |
/register |
Server | Registration; creates user + welcome book + entry |
/dashboard |
Server | Bookshelf — lists all journals for the user |
/journal/[bookId] |
Server | Open book reader/writer (bookId is cuid, not slug) |
/api-status |
Server | Live API dependency health + aggregate stats (auth required) |
/api-documentation |
Server | In-app OpenAPI-style route catalog (auth required) |
/robots.txt |
Metadata | Disallows /api, /dashboard, /journal for crawlers |
/sitemap.xml |
Metadata | Public indexable routes (/, /login, /register) |
Route groups (auth) and (dashboard) do not affect the URL — they organize layouts only.
Auth and dashboard pages use export const dynamic = "force-dynamic" so session data is always fresh.
All JSON responses follow a common envelope:
{
"success": true,
"message": "Optional human message",
"data": {}
}| Method | Path | Auth | Description |
|---|---|---|---|
| GET, POST | /api/auth/[...nextauth] |
— | NextAuth session, sign-in, sign-out, OAuth callbacks |
| POST | /api/auth/register |
— | Register user; seeds default book + welcome entry |
| GET | /api/books |
✅ | List user's journals with entry counts |
| POST | /api/books |
✅ | Create journal + starter entry (transaction) |
| GET | /api/books/[bookId] |
✅ | Book detail + entries (ownership scoped) |
| PATCH | /api/books/[bookId] |
✅ | Update title, cover, theme; slug sync on title change |
| DELETE | /api/books/[bookId] |
✅ | Delete book and cascade entries |
| POST | /api/entries |
✅ | Create entry in owned book |
| PATCH | /api/entries/[entryId] |
✅ | Autosave / manual save; recalculates wordCount, excerpt, slug |
| DELETE | /api/entries/[entryId] |
✅ | Delete entry (ownership scoped) |
| GET | /api/search |
✅ | Scoped entry search (q, optional bookId, mood) |
| GET | /api/journal/events |
✅ | SSE stream — journal mutations (Redis pub/sub) |
| POST | /api/ai/assist |
✅ | Sync AI continuation (rate limited) |
| POST | /api/ai/assist/stream |
✅ | SSE streaming AI continuation |
| POST | /api/voice/transcribe |
✅ | Phase 3 voice STT proxy (Deepgram / AssemblyAI; rate limited) |
| GET | /api/health |
— | Simple health check for monitoring |
| GET | /api/status |
✅ | Dependency health + platform/personal aggregate stats |
| GET | /api/openapi |
✅ | Machine-readable route catalog JSON |
Authorization pattern (every protected route):
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, message: "Unauthorized" },
{ status: 401 },
);
}
// Prisma queries always include userId in where clauseBrowse the full catalog interactively at /api-documentation when logged in, or fetch GET /api/openapi.
Three main models in prisma/schema.prisma:
Stores account credentials and profile. passwordHash is null for OAuth-only users. Relations: books[], entries[].
A journal on the shelf. Unique constraint: @@unique([userId, slug]). Fields include coverColor, coverEmoji (icon slug), theme, visibility.
A single diary page. tags is stored as a JSON string (e.g. "[\"morning\",\"coffee\"]") — parsed in app code with parseTags(). Unique: @@unique([bookId, slug]).
Cascade deletes: Deleting a user removes their books and entries.
model JournalEntry {
id String @id @default(cuid())
bookId String
title String @default("Untitled Entry")
content String @default("")
mood String @default("✨")
weather String @default("☀️")
tags String @default("[]")
wordCount Int @default(0)
entryDate String
// … see prisma/schema.prisma for full model
}There is no .env file in the repository (by design — secrets must not be committed). Copy the template:
cp .env.example .envYou cannot authenticate or persist journals without a database and auth secret. These four variables are required:
| Variable | Description | How to obtain |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string for Prisma queries | Neon, Supabase, Railway, local Docker, or any Postgres host |
DIRECT_URL |
Direct Postgres URL for migrations/db push |
Same as DATABASE_URL unless you use a pooler (then use direct host for migrations) |
AUTH_SECRET |
NextAuth JWT signing secret | Run: openssl rand -base64 32 |
NEXTAUTH_URL |
Public app URL | http://localhost:3000 locally; https://your-domain.vercel.app in production |
Example .env for local development:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/storybook"
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/storybook"
AUTH_SECRET="your-generated-secret-here"
NEXTAUTH_URL="http://localhost:3000"With only these four variables, the app runs fully: email/password auth, shelf, journal CRUD, autosave, offline drafts, and search. Optional features simply stay disabled.
| Variable | Feature | Behavior when missing |
|---|---|---|
GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET |
Google OAuth login | Google button hidden; email/password still works |
GOOGLE_ID + GOOGLE_SECRET |
Legacy aliases | Same as above |
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN |
SSE realtime + AI rate limit | In-memory rate limit fallback; no cross-tab SSE |
GROQ_API_KEY |
AI assist (primary) | Falls through to OpenRouter, then Anthropic |
OPENROUTER_API_KEY |
AI assist (fallback) | Falls through to Anthropic |
ANTHROPIC_API_KEY |
AI assist (legacy) | Returns poetic placeholder text; UI still works |
SHOW_DEMO_LOGIN |
Demo account dropdown on /login |
Defaults to on; set "false" to hide in production |
DEEPGRAM_API_KEY |
Voice dictation Phase 3 (Deepgram) | Phase 1/2 still work; set NEXT_PUBLIC_VOICE_PROVIDER=server-deepgram to expose in picker |
ASSEMBLYAI_API_KEY |
Voice dictation Phase 3 (AssemblyAI) | Phase 1/2 still work; set NEXT_PUBLIC_VOICE_PROVIDER=server-assemblyai to expose in picker |
NEXT_PUBLIC_VOICE_PROVIDER |
Default cloud STT provider in mic picker | server-deepgram or server-assemblyai; server keys stay server-side |
Voice dictation works without any keys. Phase 1 uses the browser Web Speech API (Chrome/Edge). Phase 2 runs Whisper on-device via @huggingface/transformers — PCM decode on main thread, ONNX ASR in a dedicated Web Worker (whisper.worker.ts), ~140 MB model download on first use. Phase 3 is optional cloud STT:
| Provider | Free tier | Sign up |
|---|---|---|
| Deepgram | $200 one-time credit, no credit card; expires 1 year after signup | console.deepgram.com/signup |
| AssemblyAI | $50 one-time credit, no credit card; credits do not expire | assemblyai.com/dashboard/signup |
After creating an API key, add it to .env and set the matching NEXT_PUBLIC_VOICE_PROVIDER so the mic button provider picker includes the cloud option.
Vercel / production: If you only use Phase 1 (Web Speech) and Phase 2 (browser Whisper), omit DEEPGRAM_API_KEY, ASSEMBLYAI_API_KEY, and NEXT_PUBLIC_VOICE_PROVIDER entirely — no empty placeholders needed.
| Environment | Location |
|---|---|
| Local | .env or .env.local in project root |
| Vercel | Project → Settings → Environment Variables |
| CI | GitHub Actions secrets or your pipeline |
See .env.example for commented templates including Docker Postgres lines.
- Node.js 20+ (LTS recommended)
- npm 10+
- PostgreSQL 14+ (hosted or local)
# 1. Clone and enter the project
git clone https://github.com/arnobt78/StoryBook-Journal-Smart-Diary--NextJS-FullStack.git
cd StoryBook-Journal-Smart-Diary--NextJS-FullStack
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.example .env
# Edit .env — set DATABASE_URL, DIRECT_URL, AUTH_SECRET, NEXTAUTH_URL
# 4. Prepare database
npx prisma generate
npm run db:push
npm run db:seed # optional demo data
# 5. Start dev server
npm run devnpm run verify # lint + typecheck + 123 unit testsWhen the demo login picker is enabled on /login:
| Field | Value |
|---|---|
test@user.com |
|
| Password | 12345678 |
The test account is created automatically when the first real user registers (idempotent seed in the register API route).
If you do not have a hosted database:
docker compose up -d dbThen use the Docker lines from .env.example:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/storybook"
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/storybook"Run npm run db:push again. Docker runs Postgres only — the Next.js app runs with npm run dev, not in Docker.
- Push repo to GitHub.
- Import project in Vercel.
- Add all required env vars in the dashboard.
- Deploy — Vercel runs
next buildautomatically. - For Google OAuth, add your Vercel URL to Google Cloud Console authorized origins and redirect URIs.
| Script | Command | Purpose |
|---|---|---|
| Dev server | npm run dev |
Hot reload at localhost:3000 |
| Production build | npm run build |
Compile for deployment |
| Start production | npm run start |
Run built app |
| Verify all | npm run verify |
lint + typecheck + vitest |
| Lint | npm run lint |
ESLint check |
| Lint fix | npm run lint:fix |
Auto-fix lint issues |
| Typecheck | npm run typecheck |
tsc --noEmit |
| Unit tests | npm run test |
Vitest (123 tests) |
| E2E tests | npm run test:e2e |
Playwright (optional; needs seed) |
| Prisma generate | npm run db:generate |
Regenerate client after schema changes |
| Push schema | npm run db:push |
Sync schema to DB (dev-friendly) |
| Migrate | npm run db:migrate |
Create/apply migrations |
| Prisma Studio | npm run db:studio |
Visual DB browser |
| Seed | npm run db:seed |
Run prisma/seed.ts |
| Full setup | npm run setup |
install + generate + db push |
Follow this path if you are new to the codebase.
src/app/page.tsx— session check + landing cover.src/components/journal/BookCover.tsx— 3D hinge CSS animation.src/app/(auth)/login/page.tsxandAuthBookShell.tsx— page flip between login/register.src/hooks/useAuthBookNavigation.ts— earlyrouter.push, hold cover, stagger remount.
src/app/(dashboard)/dashboard/page.tsx— Server Component fetches books.src/components/journal/BookShelf.tsx— client shelf, create book modal, prefetch on hover.src/hooks/useJournalPrefetch.ts— warms route +bookDetailquery on spine hover.
src/app/(dashboard)/journal/[bookId]/page.tsx— loads book + entries server-side; resolves?entry=.src/components/journal/BookSpread.tsx— orchestrator: flip, autosave, offline, AI.LeftPage.tsx/RightPage.tsx— read vs write UI with stagger animations.src/hooks/usePageFlip.ts— animation timing and re-entrancy guard.src/lib/journal-entry-url.ts—history.replaceStatefor entry focus persistence.
src/app/api/entries/[entryId]/route.ts— PATCH autosave handler.src/lib/validations.ts— Zod schemas shared with API.src/lib/journal-mutation.ts—afterJournalMutation()for Redis publish.src/lib/api-route-catalog.ts— static docs for/api-documentation.
src/lib/query-keys.ts— central cache keys (journalSubtree,apiStatus, …).src/lib/journal-cache-notify.ts— the only place to invalidate journal queries.src/components/auth/OAuthReturnSync.tsx— post-OAuth invalidation pattern.
src/lib/offline/— IndexedDB drafts + sync queue.src/hooks/useOfflineSyncQueue.ts— FIFO drain on reconnect.src/context/OfflineSyncContext.tsx— global pending count for nav badge.
src/hooks/useJournalRealtime.ts— SSE →notifyJournalCacheUpdated.src/app/(dashboard)/api-status/page.tsx— operational dashboard.src/app/(dashboard)/api-documentation/page.tsx— developer docs UI.
Many files under src/ carry @file + WALKTHROUGH block comments at the top. These are educational only — they do not change runtime behavior.
| Pattern | Meaning |
|---|---|
@file path/relative/to/src |
Which module you are reading |
WALKTHROUGH — … |
High-level purpose and how it fits the app |
── Section ── inside components |
Feature-specific notes (offline, flip, stagger, cache) |
Recommended reading order: BookSpread.tsx → journal-cache-notify.ts → journal-api.ts → useOfflineSyncQueue.ts → AuthBookShell.tsx → api-route-catalog.ts.
These pieces are designed to be copied into other Next.js projects.
Wraps next/image with a fallback chain: primary URL → Robohash → native <img>.
Use when: loading user avatars from Google/GitHub where URLs may 404.
import { SafeImage } from "@/components/ui/safe-image";
<SafeImage
src={session.user.image}
alt="Avatar"
width={32}
height={32}
fallbackSrc={`https://robohash.org/${email}?size=64x64`}
/>;Water-splash click effect with optional shine radius. Used on auth CTAs, shelf spines, and journal nav.
import { RippleButton } from "@/components/ui/ripple-button";
<RippleButton shineRadius={120} className="leather-glass-btn">
Save
</RippleButton>;Manages flip animation state; calls your callback only after the animation ends (prevents blank flashes during navigation).
const { isFlipping, flipDir, triggerFlip } = usePageFlip();
triggerFlip("fwd", () => {
router.push("/next-page"); // safe — runs after flip completes
});Single invalidation entry point for all journal-related TanStack Query data.
import { notifyJournalCacheUpdated } from "@/lib/journal-cache-notify";
await notifyJournalCacheUpdated(queryClient);Accessible async confirmation modal with loading state.
Use when: destructive actions (delete entry, delete book).
Copy src/lib/offline/* + useOfflineSyncQueue for queue-and-drain semantics on any REST API.
Next.js 16+ edge boundary — redirect unauthenticated users before protected pages render. API routes still call auth() independently.
Theme tokens in src/constants/themes.ts map to CSS variables via bookThemeCssVars(). Reuse for any multi-theme document UI.
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"securepass123","displayName":"You"}'const res = await fetch("/api/books", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Travel Log",
coverColor: "#1a3a5c",
coverEmoji: "plane",
theme: "midnight-journal",
description: "Road trips and memories",
}),
});
const { data } = await res.json(); // { id: "cuid...", ... }await fetch(`/api/entries/${entryId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Morning thoughts",
content: "<p>Today I learned...</p>",
mood: "☕",
weather: "🌤️",
tags: ["learning", "morning"],
}),
});// src/app/(dashboard)/dashboard/page.tsx (simplified)
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/db";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user?.id) redirect("/login");
const books = await prisma.journalBook.findMany({
where: { userId: session.user.id },
include: { _count: { select: { entries: true } } },
orderBy: { updatedAt: "desc" },
});
return <BookShelf initialBooks={books} />;
}curl "http://localhost:3000/api/search?q=morning&limit=10" \
-H "Cookie: <your-session-cookie>"Primary keywords (defined in src/lib/site-metadata.ts):
StoryBook Journal, digital journal, online diary, journaling app, smart diary, page flip animation, immersive writing, AI writing assistant, personal journal, daily journal, offline journal, IndexedDB sync, Next.js journal, React diary app, Prisma PostgreSQL, NextAuth, TanStack Query, TipTap editor, Arnob Mahmud
Author: Arnob Mahmud · contact@arnobmahmud.com
Live demo: https://storybook-journal.vercel.app
SEO notes:
- Root metadata in
src/lib/site-metadata.ts— title, description, OpenGraph, Twitter, icons (/diary-1.svg), author. src/app/sitemap.ts—/,/login,/register.src/app/manifest.ts— web app manifest (/manifest.webmanifest).- Public landing
/is indexable with full OpenGraph/Twitter metadata. - Dashboard and journal routes set
robots: { index: false }— private user content stays out of search indexes. src/app/robots.tsdisallows/api,/dashboard,/journalfor well-behaved crawlers.
| Document | Topic |
|---|---|
docs/PROJECT_WALKTHROUGH.md |
Deep architecture map and audit notes |
docs/AUTH_UI_IMPLEMENTATION_GUIDE.md |
OAuth flicker prevention, session patterns |
docs/VERCEL_PRODUCTION_GUARDRAILS.md |
Security headers, bot protection |
docs/SAFE_IMAGE_REUSABLE_COMPONENT.md |
SafeImage implementation details |
docs/DROPDOWN_TEST_CREDENTIALS_DOCS.md |
Demo account and NextAuth reference |
.env.example |
All environment variable templates with comments |
StoryBook Journal demonstrates how to build a polished, production-style full-stack app with Next.js 16: tactile UI animations, strict auth boundaries, optimistic offline sync, centralized cache invalidation, and optional AI and realtime layers. Whether you are learning App Router patterns, Prisma modeling, TanStack Query, or offline-first design, this repository provides working code with inline walkthrough comments you can read file-by-file.
Clone it, set four environment variables, run npm run dev, and explore the live demo — then open BookSpread.tsx and follow the data from browser click to PostgreSQL row and back.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊









