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.
7.5 KiB
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—/unconditionallyredirect("/recipes"), no marketing content exists.apps/web/proxy.ts(the app's middleware,export default proxy+config.matcher) gates every path not inPUBLIC_PATHSbehind a session cookie, redirecting straight to/login./is not in that list — so today, an anonymous visitor hitting/never even reachespage.tsx's redirect; they're bounced to/loginfirst. This is the one integration point that actually matters — the vitrine is invisible to anonymous visitors untilPUBLIC_PATHSchanges.- No
/privacy,/terms,/about,/contact,/features,/pricing, or public/changelogroute exists anywhere. apps/web/lib/changelog.tsalready has aCHANGELOGarray — 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 forapp/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-intli18n (lib/i18n/*,messages/en.json/fr.json) — marketing copy gets the sameen/frtreatment as the rest of the app, new keys under amarketingnamespace,- 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 existingapp/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 setsexport 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
sitemap.ts/robots.ts+ thePUBLIC_PATHS/root-page.tsxchange (§2–3) — plumbing first, still no real content, safe to ship and verify the routing/auth-gating logic works before writing copy./(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)./changelog— cheapest page to build (data already exists), good early win./privacy+/terms+/about+/contact— required-but-not-persuasive pages, batch together./pricing— onceSTRIPE_PLAN.mdpricing (§1c) is confirmed and ideally once Checkout exists, so the CTA goes somewhere real instead of just to signup.