Cornershopdev turns an existing restaurant website—or just a restaurant name—into a private, prefilled, mobile-first website preview. The restaurant keeps its existing booking, ordering, and delivery providers. It claims the finished preview, subscribes, then connects its domain.
- Paste a restaurant URL or name.
- Import public website content with SSRF-safe fetching and bounded HTML reads.
- Recover the menu, contact details, imagery, and external integrations.
- Derive the colour palette from the source branding and select a cuisine-aware layout.
- Detect the source language, preserve it as canonical, and generate a complete English translation during the same structured AI pass.
- Preserve first-party photography and optionally enhance exposure, colour, crop, noise, and clarity without changing the food or venue.
- Save a private preview through a durable PostgreSQL-backed Workflow.
- Verify ownership through a one-time business-domain email invitation or a concierge-approved owner email.
- Claim the restaurant through invitation-bound Stripe Checkout; the completed checkout creates the prefilled owner account.
- Authorize the restaurant domain for on-demand TLS and show the exact DNS records.
- Monitor and maintain the menu, imagery, and external links from the dashboard.
Each claimed site has a tenant-scoped /dashboard workspace. Owners can edit
their site, connect a domain, review first-party booking leads, and move each
request from NEW to CONTACTED or CLOSED. Contact details are returned only
after the session is revalidated against that site's organization membership.
/admin is the platform operator console. It requires both a database
SUPERADMIN role and an email listed in SUPERADMIN_EMAILS. It shows signups,
subscriptions, request totals, portfolio traffic and conversion summaries, and
bounded per-site operational rows. It intentionally does not expose customer
lead contact details across tenants.
Analytics run only on verified customer domains. Factory pages, private preview
routes, bots, and automated browsers are excluded. The browser creates an
ephemeral visit UUID in sessionStorage and sends only:
- event UUID
- visit UUID
- site view or CTA click
- server-owned site identity derived from the verified request hostname
- server timestamp
Raw analytics events never store IP addresses, user-agent strings, referrers, paths, query strings, provider URLs, names, email addresses, phone numbers, or booking notes. A one-minute Redis limiter may use a transient hash derived from the connection address; it is not written to PostgreSQL.
Booking requests remain the authoritative lead count, so a dropped analytics
beacon cannot lose a real lead. The corresponding LEAD_CREATED event is
server-owned and best effort. Client and operator workspaces expose 7, 30, and
90-day distinct-visit, CTA-visitor, booking-lead, and conversion metrics.
Raw analytics events are retained for 120 days and pruned daily under a
PostgreSQL advisory lock.
New restaurant previews use a versioned theme registry driven by service model, primary customer intent, menu experience, brand traits, price position, location count, and photography quality:
terroir-editorial@1— reservation-led, seasonal and editorialcounter-service@1— external-order-led commerce browsingafter-dark@1— atmospheric reservations, events and late-night visits
The public registry and live renderer power /themes/restaurant. AI may choose
only these IDs plus a closed set of validated colour and presentation tokens,
plain-text reasons, confidence, and two alternatives. Unknown IDs, arbitrary
CSS/HTML/classes/components/font URLs, malformed tokens, and low-contrast
colour combinations are rejected or repaired before rendering. Missing or
invalid model output uses the deterministic scorer.
The six earlier cuisine-era templates (heritage, fresh, bold,
nocturne, coastal, and warm) remain as a compatibility renderer. A stored
restaurant without a valid structured selection keeps its existing layout;
theme adoption is never inferred from a deployment.
Dish imagery is a saved presentation setting rather than a destructive edit. Heritage and fine-dining templates default to a clean text-led menu; casual, fresh, coastal, and bold concepts default to a small highlights gallery. Owners can show or hide the gallery from the dashboard without deleting any images.
Restaurant data uses one canonical source locale plus structured translation
overlays. Prices, currencies, images, addresses, provider names, and external
booking or ordering URLs remain shared, so translating a site cannot fork its
operational data. Menu sections, menu items, descriptions, dietary labels, and
link labels keep the same order and count in every locale.
If an existing provider URL already exposes a lang parameter, the rendered
link updates only that preference while preserving the same provider and flow.
Imports read the document language when available. Non-English sources receive
an English translation in the same schema-validated OpenRouter generation.
Restaurant templates and interface copy use small server-side dictionaries.
The canonical site is available at /preview/[slug]; translations use
/preview/[slug]/[locale] and expose language alternates in metadata.
- Next.js 16 App Router and React 19
- Bun
- Tailwind CSS v4 and shadcn/ui
- Prisma 7 with PostgreSQL and the
pgdriver adapter - Vercel AI SDK 6 with OpenRouter for structured text generation and optional source-photo enhancement
- Workflow DevKit with its self-hosted PostgreSQL World
- Amazon S3 and CloudFront for persistent enhanced derivatives
- Redis for public preview rate limits
- Stripe subscriptions
- Resend passwordless sign-in links
- Caddy on-demand TLS for verified customer domains
cp .env.example .env.local
bun install
bun run devThe marketing site and deterministic preview flow work without external credentials. Production integrations activate when their environment variables are configured.
Do not run migrations against production from a local machine. Create the database, then apply the committed migrations through the reviewed release environment:
bun run db:migrate:status
bun run db:migrate:deployPreview and production service isolation, readiness checks, backups, restores,
and credential rotation are documented in
docs/operations/platform-services.md.
The one-price offer, evidence gates, founder-cost worksheet, second-lead
qualification, and 30-day decision record for the first paid restaurant are in
docs/operations/first-customer-validation.md.
The bearer-authenticated /api/health/ready route verifies PostgreSQL, Redis,
and Amazon S3 without returning secret values. Each application
instance coalesces concurrent checks and caches their aggregate result for five
seconds.
DATABASE_URL
HEALTHCHECK_TOKEN
Use distinct, randomly generated values with at least 32 bytes for Preview and Production. Readiness callers send the value as a bearer token; the endpoint fails closed when it is absent or invalid.
Restaurant crawling, same-origin page discovery, SSRF checks, contact recovery, and integration detection run locally without a model. OpenRouter is used only to normalize recovered content into a structured restaurant draft:
OPENROUTER_API_KEYOPENROUTER_TEXT_MODELdefaults toopenrouter/auto
OpenRouter Auto selects a compatible language model per import. Structured output is schema validated before it is persisted.
Optional image enhancement runs through the same key and the same provider. The
model must expose image output; the default does.
OPENROUTER_IMAGE_MODELdefaults togoogle/gemini-3.1-flash-image
Without OPENROUTER_API_KEY an import still completes: the draft falls back to
the deterministic composer and hero enhancement is skipped.
WORKFLOW_ENABLED=trueWORKFLOW_TARGET_WORLD=@workflow/world-postgresWORKFLOW_POSTGRES_URL
Configure the private production S3 bucket and its CloudFront public origin:
AWS_REGIONS3_BUCKETS3_PUBLIC_BASE_URL
Cornershopdev never creates a dish photograph from menu text. Enhancement requires an existing HTTPS source image from the restaurant, an owner upload, or customer UGC with explicit reuse permission. The immutable original URL and its provenance are stored alongside the enhanced S3 derivative.
Allowed edits are exposure, white balance, highlight and shadow recovery, denoising, sharpness, resolution, straightening, subtle cropping, and removal of transient non-material distractions such as sensor dust. Ingredients, garnishes, portions, plating, tableware, people, architecture, and material scene elements must not be added, removed, replaced, moved, or regenerated. Owners can disable automatic enhancement and must review the derivative before publishing.
Configure the isolated Redis service:
REDIS_URL
Public imports are limited to five preview generations per IP address per hour. Production fails closed when Redis is not configured, preventing an unbounded AI generation endpoint.
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_STARTER_PRICE_IDSTRIPE_GROWTH_PRICE_IDSTRIPE_LEGACY_PRICE_IDS(optional comma-separated access allowlist used during price migrations; never offered in Checkout)
Configure the webhook endpoint as:
https://cornershop.dev/api/webhooks/stripe
Test-mode verification, the exact event allowlist, retry/replay behavior,
Customer Portal setup, and the production activation blockers are documented
in docs/operations/stripe-billing.md.
Checkout requires a valid hashed claim invitation; a public preview URL alone
cannot authorize billing or ownership.
CLAIM_TOKEN_SECRETwith at least 32 random charactersRESEND_API_KEYEMAIL_FROM
PUBLIC_APP_IPCUSTOM_DOMAIN_CNAMEPLATFORM_HOSTNAMES
The application records the hostname and returns the production A or CNAME target. After DNS resolves, the owner verifies it in the dashboard. Caddy issues TLS only when its authorization callback confirms that the domain is verified and belongs to a restaurant.
The app is single-origin. Caddy on the EC2 application host terminates TLS for
every ingress the factory operates — cornershop.dev, www, api, domains,
and each customer storefront via on-demand TLS — and reverse-proxies all of them
to the one application container. No niche gets its own platform subdomain; a
niche brings only the storefront domain its customers actually type.
Leave CORNERSHOPDEV_API_ORIGIN empty. It exists for a future split deployment,
where it makes next.config.ts proxy /api/* to a separate API origin. Setting
it on a single-origin host proxies /api/* to a hostname that resolves back to
this same container, where the rewrite fires again — an infinite loop.
- Import URLs are limited to HTTP(S), DNS-resolved before every redirect, and rejected when any address is local or private.
- HTML responses are content-type checked, timeout bounded, and capped at 1.5 MB.
- AI output is validated with Zod before it enters the product.
- Existing booking and ordering links are extracted from source material and override model-generated links.
- Stripe webhooks verify the raw body signature.
- Restaurant claims require a hashed, expiring invitation bound to one site, intended email, and Stripe Checkout session. Raw invitation tokens are kept in URL fragments so embedded preview assets cannot receive them as referrers.
- Self-serve claims require the exact imported business email or an address on the exact source hostname. Ambiguous ownership requires a dual-gated superadmin approval from the operator console.
- Claim invitation requests and checkout attempts use isolated Redis rate-limit buckets and fail closed in production. Creation, verification, checkout, acceptance, and rejection events are recorded without tokens or contact data.
- Dashboard sessions are HMAC-signed, HTTP-only, same-site cookies.
- Restaurant mutations require a session matching the restaurant slug.
- Image enhancement and domain management require that same restaurant-scoped session.
- Public preview generation is rate limited and fails closed in production.
- Enhanced derivatives are persisted to private S3 storage and served through CloudFront while authentic originals and provenance remain available.
- Arbitrary restaurant images load directly in the browser instead of through the Next.js image proxy.
/— marketing and URL intake/create— import and preview studio/claim/[slug]— pricing and claim checkout/dashboard— authenticated restaurant management/dashboard?demo=1— local demo dashboard/admin— dual-gated superadmin operator console/api/analytics/events— first-party cookieless live-site event intake/preview/[slug]— private full-screen restaurant preview/preview/[slug]/[locale]— translated restaurant preview