The production website and lightweight commerce tooling for To Be Read (TBR) — the rebrand of the long-running Clackamas Book Exchange, an independent used bookshop in the Portland, Oregon area. Built on the Next.js App Router and deployed on Netlify.
🔗 Live site: https://tobereadshop.com
A real small-business website carrying real traffic — not a demo. It pairs a fast, accessible storefront with the kind of growth tooling a neighborhood shop actually needs: an AI book matchmaker, a self-publishing SEO content engine, trade-in credit estimation, wishlist alerts, and live customer reviews — all behind hardened API routes and an automated CI/audit pipeline.
- 📚 The Reading Room — a built-in content/SEO engine. 100+ hand-written
reading guides and local guides live as typed React modules in
content/reading-room/; a build step generates the registry, RSS feed, tag pages, and themed collections automatically. - 🤖 Next Read Matchmaker — AI-powered book recommendations via a Groq LLM, grounded with live DuckDuckGo results so suggestions stay current and real.
- 🔁 Trade-in credit estimator — shoppers get an instant store-credit estimate for the books they want to bring in.
- ⭐ Wishlist hunt + alerts — passwordless (magic-link) accounts let readers save titles and get emailed when a match lands on the shelves.
- 🗣️ Live reviews — first-party reviews plus a Google Places feed, with an admin moderation queue.
- 🔎 Local SEO, done properly — JSON-LD structured data (
LocalBusiness, breadcrumbs), sitemap,llms.txt, IndexNow pinging, and edge-generated Open Graph / Twitter share images that always match the brand. - 🛡️ Hardened API routes — every
app/api/*handler shares in-memory rate limiting, fetch timeouts, typed JSON errors, and graceful degradation when an upstream (DB, Groq, Google) is unavailable. - ✅ Automated quality gates — per-PR lint / typecheck / build / SQL-schema checks, plus weekly Lighthouse SEO, mobile, and dependency-security audits.
| Concern | Choice |
|---|---|
| Framework | Next.js 16 (App Router, React 19) |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS v4 + shadcn/ui (Radix primitives) |
| Animation | Framer Motion |
| Database | Neon Postgres (provisioned via Netlify DB) |
| Auth | Auth.js / NextAuth v5 (magic-link email) |
| Resend (magic links + wishlist hunt notifications) | |
| AI | Groq (LLM) + DuckDuckGo grounding (Next Read Matchmaker) |
| Hosting | Netlify (@netlify/plugin-nextjs) |
Note on the database: the app uses a single Neon Postgres instance reached through
@neondatabase/serverless. There is no Supabase in this project. The separateechoes/subproject (an experimental applet) uses Firebase / Firestore and is built and deployed independently.
npm install
cp .env.example .env.local # then fill in the values below
npm run dev # http://localhost:3000See .env.example for the full annotated list. The important ones:
| Variable | Required | Purpose |
|---|---|---|
NETLIFY_DATABASE_URL |
yes* | Neon connection string (auto-set on Netlify). |
DATABASE_URL |
— | Local-dev fallback when not on Netlify. |
AUTH_SECRET |
yes | NextAuth signing secret (openssl rand -base64 32). |
RESEND_API_KEY |
yes | Magic-link + notification email delivery. |
RESEND_FROM_EMAIL |
yes | Verified sender identity. |
GROQ_API_KEY |
yes | Next Read Matchmaker LLM (DuckDuckGo grounding is keyless). |
GROQ_MODEL |
no | Override the matchmaker model (default llama-3.3-70b-versatile). |
ADMIN_EMAILS |
yes | Comma-separated allowlist for /admin. |
GOOGLE_BOOKS_API_KEY |
no | Lifts the unauthenticated Google Books rate limit. |
GOOGLE_PLACES_* |
no | Live Google reviews on /connect and /visit. |
* Either NETLIFY_DATABASE_URL or DATABASE_URL must be set for any
database-backed route to work. Routes fail gracefully (not at build time) when
the database is unavailable.
| Script | What it does |
|---|---|
npm run dev |
Start the dev server. |
npm run build |
Production build. |
npm run start |
Serve the production build. |
npm run lint |
ESLint (Next.js config). |
npm run typecheck |
tsc --noEmit — strict type checking. |
npm run seed:arrivals |
Seed the recent_arrivals table. |
Run npm run lint && npm run typecheck && npm run build before pushing — this
mirrors the CI workflow exactly.
The schema lives in db/schema.sql and is idempotent —
safe to re-run. Apply it once against the Neon instance:
psql "$NETLIFY_DATABASE_URL" -f db/schema.sqlTables: NextAuth auth tables (users, accounts, sessions,
verification_tokens), plus the application tables recent_arrivals and
wishlists. Hot query paths are indexed (recent_arrivals_added_at_idx, a
partial index for pending wishlist notifications, etc.).
app/ App Router routes + API handlers (app/api/*)
components/ React components (sections, UI primitives in components/ui)
content/ The Reading Room — guides authored as typed React modules
lib/ Shared server/client helpers
db.ts Neon client + shared row types
server/ Server-only hardening: rate limiting, fetch timeouts
db/ SQL schema + seed scripts
scripts/ Build-time tooling (Reading Room registry, IndexNow)
echoes/ Independent Firebase applet (own package.json + deploy)
.github/workflows/ CI + scheduled audits
All routes under app/api/ run on the Node.js runtime and share the hardening
helpers in lib/server/functionHardening.ts (in-memory IP rate limiting,
fetchWithTimeout, withTimeout). They validate input, return typed JSON
errors with appropriate status codes, and degrade gracefully when an upstream
(DB, Groq, DuckDuckGo, Google) is unavailable.
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
every PR + push main |
Lint, typecheck, build + schema check — merge gate. |
daily-seo-audit.yml |
weekly (Mon 06:00 UTC) | SEO metadata checks + Lighthouse SEO. |
daily-dependency-security.yml |
weekly (Mon 07:00 UTC) | npm audit for root + echoes. |
daily-mobile-optimization.yml |
weekly (Mon 08:00 UTC) | Viewport/manifest checks + Lighthouse. |
ci.yml also runs a schema check: it applies db/schema.sql to a throwaway
Postgres twice to catch broken SQL and verify the file stays idempotent before
it reaches the live Neon database.
The scheduled audits run weekly (not daily) and use concurrency guards to
avoid stacking runs — per-PR correctness is already enforced by ci.yml, so the
heavier audits only need a periodic cadence. They also surface Lighthouse scores
in each run's Step Summary with a shareable report link. Every scheduled
workflow supports workflow_dispatch for on-demand runs.
Deployment is handled by Netlify via netlify.toml (npm run build, published
from .next).
Visit the shop in person: 7931 SE King Rd, Unit 1, Portland, OR 97222 · https://tobereadshop.com