Files
Epicure/VITRINE_PLAN.md
T
Arnaud e4ad092751 docs: drop /changelog from vitrine plan, flag in-app "what's new" gap
CHANGELOG.ts is a dev log (migration reminders, bug-fix jargon), not
fit for public display. Removed the public changelog page from the
vitrine's page list/rollout/PUBLIC_PATHS example. Added §6 flagging
the real underlying need — a curated, in-app "What's New" surfaced to
existing users (bell/badge, keyed off last-seen version) — as a
separate, smaller feature to scope properly later, not folded into
this plan.
2026-07-18 01:35:35 +02:00

94 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, or `/pricing` route exists anywhere.
- 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 |
| `/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", "/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 7 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. `/privacy` + `/terms` + `/about` + `/contact` — required-but-not-persuasive pages, batch together.
4. `/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.
---
## 6. Separate concern: advertising new features to existing users
Not part of this plan (in-app, not public/marketing), but flagged since it
came up here: `apps/web/lib/changelog.ts`'s `CHANGELOG` array is written
for you, not users — entries mix real feature announcements with internal
notes ("not applied in sandbox — run `pnpm db:migrate`", bug-fix jargon,
migration reminders). That's correctly *not* public. But there's a real
gap: users have no clean way to learn what shipped since they last looked.
Direction for later, not scoped in detail here: a curated, user-facing
"What's New" — either a hand-picked subset/rewrite of `CHANGELOG` entries
(only the ones worth telling a user about, in plain language, not the raw
dev log) or a separate small table if the two need to diverge more than a
filter allows. Surfaced in-app — a badge/dot on a bell icon, a dismissible
panel on next login, keyed off "last version the user has seen" so it only
shows genuinely new items. This is a distinct, smaller feature from the
vitrine and can be scoped properly in its own pass.