diff --git a/VITRINE_PLAN.md b/VITRINE_PLAN.md new file mode 100644 index 0000000..68ad7cb --- /dev/null +++ b/VITRINE_PLAN.md @@ -0,0 +1,76 @@ +# 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`: +```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 (§2–3) — 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.