Files
Epicure/VITRINE_PLAN.md
T
Arnaud 634f8245e1 docs: add vitrine (marketing site) plan
Page list (home/features/pricing/changelog/about/privacy/terms/contact),
same-app route-group approach reusing design system/i18n/deploy, and
the one integration detail that actually gates visibility: proxy.ts's
PUBLIC_PATHS currently sends every anonymous visitor of "/" straight
to /login before page.tsx's redirect-to-/recipes ever runs — the
marketing site is invisible until that changes, plus a startsWith-vs-
exact-match gotcha for adding "/" itself to that list. Also flags
missing sitemap.ts/robots.ts and legal-content review as open gaps.
2026-07-18 00:52:36 +02:00

7.5 KiB
Raw Blame History

Vitrine (Marketing Site) Plan

Status: planning only — nothing in this doc is implemented yet

Current state, confirmed by reading the actual code, not assumed:

  • apps/web/app/page.tsx/ unconditionally redirect("/recipes"), no marketing content exists.
  • apps/web/proxy.ts (the app's middleware, export default proxy + config.matcher) gates every path not in PUBLIC_PATHS behind a session cookie, redirecting straight to /login. / is not in that list — so today, an anonymous visitor hitting / never even reaches page.tsx's redirect; they're bounced to /login first. This is the one integration point that actually matters — the vitrine is invisible to anonymous visitors until PUBLIC_PATHS changes.
  • No /privacy, /terms, /about, /contact, /features, /pricing, or public /changelog route exists anywhere.
  • apps/web/lib/changelog.ts already has a CHANGELOG array — currently only rendered at /admin/changelog (admin-only). Directly reusable for a public changelog page, no new data source needed.
  • No sitemap.ts/robots.ts — Next.js supports both as file-based special routes (same convention already used for app/manifest.ts, seen in that file) — currently missing entirely, worth adding as basic SEO hygiene regardless of how much content the vitrine ends up with.

1. Page list

Route Purpose
/ Home — hero, value prop, 3-4 feature highlights with screenshots, primary CTA → /signup
/features Deeper tour: AI recipe generation, meal planning, pantry tracking, social (follows/ratings/comments), the cooking assistant + its tool-calling (create recipe / add to shopping list from chat)
/pricing Free/Pro/Family comparison table, CTA into Checkout once Stripe ships (STRIPE_PLAN.md §5/§7), or straight to /signup before it does
/changelog Public version of the existing CHANGELOG data — social proof of active development, good for conversion and for existing users checking what's new
/about Story/mission — why this exists, who's building it
/privacy Privacy policy — needed once collecting emails regardless of Stripe timing (GDPR, since you're in France/EU per STRIPE_PLAN.md §10)
/terms Terms of service
/contact Support email / simple contact form

Not in v1, deferred: a blog (/blog) — real SEO value but ongoing content-production cost that's a separate commitment from building the site itself; add later if organic-search growth becomes a priority.


2. Where it lives — same app, new route group

apps/web/app/(marketing)/ — a new route group alongside the existing (auth)/(app) ones, same conventions (shared layout.tsx for the marketing nav/footer, page-per-route). This reuses:

  • the design system (components/ui/*, Tailwind config, dark/light theming already wired app-wide),
  • next-intl i18n (lib/i18n/*, messages/en.json/fr.json) — marketing copy gets the same en/fr treatment as the rest of the app, new keys under a marketing namespace,
  • the existing deploy pipeline (DEPLOY.md, Docker/Traefik) — no second build/deploy to maintain.

Root page change: apps/web/app/page.tsx currently always redirects to /recipes. Change to: authenticated session → redirect to /recipes (unchanged behavior for existing users); no session → render the (marketing) home page instead of redirecting. Concretely, move the marketing home content into app/(marketing)/page.tsx (or a shared component) and have root page.tsx branch on auth.api.getSession(...) the same way every other server component in this app already checks session (see app/(app)/recipes/page.tsx for the exact pattern to mirror).

Middleware change (the one that actually gates visibility) — add the marketing routes to PUBLIC_PATHS in apps/web/proxy.ts:

const PUBLIC_PATHS = [
  "/login", "/signup", /* ...existing... */,
  "/", "/features", "/pricing", "/changelog", "/about", "/privacy", "/terms", "/contact",
];

Note / needs an exact match, not startsWith — the current check is pathname.startsWith(p), which is fine for /features etc. (nothing else starts with those prefixes) but "/" as a prefix would make PUBLIC_PATHS.some(p => pathname.startsWith(p)) true for literally every path, since every path starts with /. This needs a small tweak to the matching logic, not just an array addition — e.g. check pathname === "/" as its own condition alongside the startsWith list, don't add bare "/" into the array.

Nav: the existing authenticated Nav component (apps/web/components/layout/nav.tsx) doesn't apply here — the marketing route group gets its own simple header/footer (logo, nav links to the pages above, Login/Signup buttons) inside (marketing)/layout.tsx, not reused from the app shell.


3. SEO basics (currently entirely missing)

  • app/sitemap.ts — list the 8 marketing routes (file-based Next.js route, same pattern as the existing app/manifest.ts).
  • app/robots.ts — allow marketing routes, disallow /api, /admin, (app) authenticated routes, /r///s///u/ if those shouldn't be indexed (check current intent — they're public-but-unlisted-ish today, decide explicitly rather than defaulting).
  • Per-page generateMetadata (title/description/OpenGraph image) — every existing app page already sets export const metadata: Metadata = {} as a placeholder pattern; marketing pages should actually fill these in, they're the ones search engines/social shares see.
  • One shared OG image (or per-page) — doesn't need to be fancy, just present; a page with no OG image looks broken when shared on socials, more damaging than a plain one.

4. Content sourcing

  • Screenshots — real app screenshots (recipe detail page, meal planner, the cooking-assistant proposal card) sell the product better than illustrations for a tool like this. Needs light art-direction (consistent browser-frame chrome, consistent sample data — a demo/seed account with good-looking recipes, not someone's messy real account) but no dedicated design tooling.
  • Copy — feature descriptions can mostly be extracted from what already exists in messages/en.json (feature names, help text) as a starting draft, then punched up for marketing tone rather than in-app instructional tone — those two registers are legitimately different and shouldn't just be copy-pasted.
  • Privacy/Terms — templated legal content exists (e.g. via a generator, or your accountant/lawyer per STRIPE_PLAN.md §10 can point you to a template suited to a French micro-entreprise) — not something to draft from scratch or have generated without review, this is exactly the kind of content where a wrong clause has real consequences. Flag as needs non-engineering review before publishing, same spirit as the Stripe plan's tax-advice caveat.

5. Rollout order

  1. sitemap.ts/robots.ts + the PUBLIC_PATHS/root-page.tsx change (§23) — plumbing first, still no real content, safe to ship and verify the routing/auth-gating logic works before writing copy.
  2. / (home) + /features — the two pages that actually sell the product; ship these before pricing exists if Stripe isn't live yet (CTA → signup, not checkout).
  3. /changelog — cheapest page to build (data already exists), good early win.
  4. /privacy + /terms + /about + /contact — required-but-not-persuasive pages, batch together.
  5. /pricing — once STRIPE_PLAN.md pricing (§1c) is confirmed and ideally once Checkout exists, so the CTA goes somewhere real instead of just to signup.