Skip to content
All packs

Capability pack

Brochure site

The smallest useful fork, and the one a real business asks for first: pages an owner can edit without a deploy, a form that reaches somebody, and enough structured data that a search engine understands who is behind it. No third-party provider is needed to run it — mail prints to the console until a key is set — but it is not a static site: pages, media, and the inbox live in a Postgres database, and editing them takes one admin account. A fork that wants no database at all keeps the blog (MDX in git) and drops the CMS.

Capabilities

11

All of them are built.

Watched in production

0

11 have never been watched against a real provider.

Provider variables for a fresh project

9

Including the packs it sits on. 9 unset here, each running its documented fallback.

Needed regardless of providers

  • A Postgres database — pages, media, inquiries, and revisions live there.
  • One admin account, to edit pages and read the inbox (the accounts pack's sign-in, without offering sign-up to visitors).

What is in it

Each row links to where it lives, and carries the state it has in THIS deployment.

  • A non-developer writes Markdown in the admin area and it is served at /<slug>. Drafts are 404 until published or opened through a day-long signed link. The sanitizer, not the author, decides what HTML exists; a reserved-slug test stops a page shadowing a route.

    Page table, Markdown through rehype-sanitize, signed preview links

  • Posts are files in the repo, validated and synced into Postgres at deploy; drafts are excluded in the query, not the template.

    next-mdx-remote + a deploy-time sync

  • An admin uploads images once and pastes a snippet into any page. Several images on one line render as a gallery grid; a quote with an attribution line renders as a testimonial card — conventions in CSS, not a page builder. The sanitizer admits same-origin image paths alongside https; a test keeps protocol-relative hosts out.

    Needs R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_PUBLIC_BASE_URL. Without it: Images are stored locally under .uploads/.

    The upload flow with an admin-only purpose, a list, a Markdown snippet, delete

  • A visitor describes a job and attaches up to six photos that go straight to storage; the request is stored before it is mailed, then acknowledged to the sender and announced to the owner. A guest's photo is bound to an httpOnly cookie, claimable only by their own submission, and swept if nothing claims it in a day. Contact messages land in the same inbox.

    Needs RESEND_API_KEY. Without it: The inquiry is stored and the notification prints to the console.

    Inquiry table, guest-owned presigned photos, an admin inbox, two templated mails

  • One schema that validates on the client and re-validates on the server.

    Zod

  • Transactional email

    Fallback

    One spec renders both an HTML email and its plain-text alternative; rich by default, MAIL_FORMAT=text for plain only. Two transports; real delivery is a key in .env. Previews at /admin/mail.

    Needs RESEND_API_KEY. Without it: Messages print to the server console in full, links included.

    Resend (or console transport)

  • SEO surface

    Ready

    robots.txt, a database-driven sitemap.xml, a web manifest, favicons, and a per-post and per-product OpenGraph image — all generated from code, no binary assets to keep in sync.

    Next metadata routes + next/og

  • Every page names its publisher: an Organization, or the configured LocalBusiness type with address, phone, hours, and service area when SITE_BUSINESS is filled in. Posts carry Article, products carry Product with an Offer and stock state, admin pages carry WebPage, all with breadcrumbs. The serializer escapes anything that could close the script element, and a test proves it.

    schema.org builders in src/lib/seo, one <script type="application/ld+json"> component

  • Analytics

    Fallback

    One beacon per navigation counts a view per (day, path) and a distinct visitor per day from a salted hash that cannot be linked across days. No cookie, no ip stored, crawlers and private pages excluded, Global Privacy Control honored. /admin/analytics charts real rows.

    Needs NEXT_PUBLIC_VERCEL_ANALYTICS. Without it: First-party cookieless page views are recorded and charted at /admin/analytics.

    First-party page views (cookieless); Vercel Web Analytics beside it when enabled

  • Bot protection

    Fallback

    Sign-up, forgot-password, and contact carry a hidden field a person never fills and a script always does. With Turnstile keys set, an invisible challenge is verified server-side too; the CSP admits the widget by derivation. One generic message for every failure.

    Needs NEXT_PUBLIC_TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY. Without it: The honeypot field runs alone, which catches naive scripts.

    Honeypot always; Cloudflare Turnstile when configured

  • Rate limiting

    Ready

    Keyed per caller, so an abuser is throttled and not the site. Durable by default: one atomic upsert per hit holds the limit across serverless instances; RATE_LIMIT_STORE=memory keeps the per-process limiter, which is also the fallback if the database call fails.

    Postgres fixed windows (atomic upsert), or in-memory

First steps in a fork

  1. 1Edit src/lib/site.ts — the name, tagline, canonical URL, and the local-business facts structured data is built from.
  2. 2Replace the palette in src/app/globals.css, keeping one flavor and deleting the rest.
  3. 3Write the first pages at /admin/pages; give the ones that belong in the footer a nav label.
  4. 4Set CONTACT_TO, then RESEND_API_KEY when you want the inbox to reach a real mailbox.

What to delete

  • The shop, forum, game, community, organizations, AI, API-key, newsletter, notification, realtime, i18n, dashboard, Lab, and inventory routes, libraries, and components.
  • Their models and enums in prisma/schema.prisma — 40 of 58 — and every relation field pointing at them; then one squashed migration with the search triggers carried by hand.
  • Sixteen places those domains reached into the shell this pack keeps: the layout's cart and unread counts, the header's cart and bell, the footer's inventory line, the sweep's commerce steps, the sitemap, the export, health. docs/knowledge-base/extraction.md lists each.

Provider variables a fresh project would set

  • NEXT_PUBLIC_TURNSTILE_SITE_KEY
  • NEXT_PUBLIC_VERCEL_ANALYTICS
  • R2_ACCESS_KEY_ID
  • R2_ACCOUNT_ID
  • R2_BUCKET
  • R2_PUBLIC_BASE_URL
  • R2_SECRET_ACCESS_KEY
  • RESEND_API_KEY
  • TURNSTILE_SECRET_KEY

Shaded green: set on this deployment.

The full inventory, with the tests that cover each capability and what has actually been watched working.

Capability inventory