mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
master
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
21a27eeb4f |
feat(www): canonicalize homepage markdown at /index.md (#49384)
The www root markdown lived at an accidental URL: `/.md` served the
homepage markdown only because middleware strips the `.md` suffix and
the empty slug fell through to the homepage allowlist entry, while the
canonical-looking `/index.md` 404'd. The served markdown also opened
with stale legacy positioning copy that no longer matches the site. I
renamed the homepage content slug to `index` end-to-end so `/index.md`
is the one canonical markdown URL.
**Changed:**
- **`/index.md` serves the homepage markdown (200 `text/markdown`)**:
`content/md/homepage.md` renamed to `index.md`; the middleware bare-root
slug mapping, the generator's sort special-case, and the homepage
alternate tag follow, so the tag now advertises `/index.md`.
- **Legacy aliases 308 to the canonical URL**: `/.md`, `/homepage.md`,
and bare `/index` redirect via `lib/redirects.js`; `/llms/homepage.txt`
retargeted straight to `/index.md` to avoid a redirect chain. New
`next.config.test.ts` assertions pin all four.
- **Positioning refreshed**: the markdown now opens with "Supabase is
the Postgres development platform" (matching the site title), replacing
the outdated tagline.
- **Generator safety**: the redirect-exclusion filter in
`generateMdContent.mjs` now exempts the `index` slug (its HTML page is
`/`, not `/index`, so a `/index` redirect never refers to it), and the
build fails if `content/md/index.md` ever goes missing while middleware
still maps `/` to the `index` slug.
- **CI actually runs the new assertions**: I widened the `www-tests.yml`
paths filter to include `apps/www/lib/**/*.js`,
`apps/www/content/md/**`, and `apps/www/scripts/**/*.mjs`. It previously
only matched `.ts*` and the next.config files, so a PR touching only
`lib/redirects.js`, the markdown content, or the generator would skip
the tests that pin these redirects.
**Note:** the existing homepage alternate tag still exists, re-pointed
to the canonical URL. Whether the homepage should advertise a markdown
sibling at all is a separate decision; leaving it aimed at a 308 would
break tag consumers. Positioning wording is editorial, happy to tweak.
## To test
Tested on Vercel preview:
- [x] `curl -si <preview>/index.md`: expect 200 `content-type:
text/markdown`, body opens with the Postgres development platform
positioning and no longer contains the old tagline
- [x] `curl -sI <preview>/.md`: expect 308 with `location: /index.md`
- [x] `curl -sI <preview>/homepage.md` and `curl -sI
<preview>/llms/homepage.txt`: expect 308 with `location: /index.md`
- [x] `curl -sI <preview>/index`: expect 308 with `location: /`
- [x] `curl -s -H "Accept: text/markdown" -o /dev/null -w "%{http_code}
%{content_type}" <preview>/`: expect `200 text/markdown` (bare-URL
negotiation unchanged)
- [x] `curl -s <preview>/ | grep -o 'type="text/markdown"
href="[^"]*"'`: expect href ending `/index.md`
## Linear
- fixes GROWTH-1117
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added support for `/index.md` as the canonical Markdown representation
of the homepage.
- Added permanent redirects for legacy homepage Markdown and text URLs.
- Added `/index` to `/` redirect handling.
- **Bug Fixes**
- Updated homepage metadata, alternate links, Markdown negotiation, and
content generation to consistently use the new canonical path.
- Improved homepage content description.
- **Tests**
- Expanded coverage for homepage Markdown routes, redirects, and URL
matching.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
f208743432 |
fix(www): changelog md negotiation slug-set gate (#49357)
Bare-URL `Accept: text/markdown` negotiation never fires on changelog entries authored after the GitHub-discussions backfill: the middleware gate `/^changelog\/\d+/` only matches legacy numeric slugs (from `legacy_gh_discussion` frontmatter), so agents that signal markdown via Accept get HTML on every new entry. I found this in the independent review round on #48475; pre-existing, not introduced there. **Changed:** - **Non-legacy entries negotiate markdown**: `generateMdContent.mjs` now lists `public/changelog/*.md` (written moments earlier by `generateStaticContent.mjs` in the same `content:build:core` chain) and emits a `CHANGELOG_PAGES` set into the generated module; the middleware regex becomes a set lookup, so negotiation coverage derives from the exact static files served and can't drift from what's published. - **Unknown and deep changelog paths stop negotiating**: the old regex prefix-matched paths like `changelog/100/bar` and nonexistent numeric slugs, rewriting them to missing `.md` files (404 under a markdown Accept); they now pass through to the dynamic route's canonicalizing 308/404. - **Build guard**: zero collected changelog slugs on Vercel fails the build (today a zero-entry changelog fetch ships empty output with a green build), and a shape assertion fails the build if collected slugs ever lose the `changelog/` prefix the middleware matches on. Locally without `CHANGELOG_SYNC_APP_*` secrets it warns and changelog negotiation is off, matching the absent content. - **`/changelog` index gated the same way**: the index slug is emitted into the set only when `public/changelog.md` was generated, replacing the hardcoded `slug === 'changelog'` branch; locally without secrets the index no longer rewrites to a nonexistent file. **Note:** script order in `content:build:core` is load-bearing (static content generation must precede md content generation); the Vercel guard turns a reorder into a loud build failure instead of a silent empty gate. ## To test Tested on the Vercel preview (`zone-www-dot-com` deployment of head `f451da3`): - [x] `curl -sI -H "Accept: text/markdown" <preview>/changelog` and `curl -sI <preview>/changelog.md`: got 200 `text/markdown` (index via the generated gate) - [x] `curl -sI -H "Accept: text/markdown" <preview>/changelog/pipelines`: got 200 `text/markdown` (prod today returns `text/html`) - [x] Same curl against the legacy numeric slug `48235-migration-of-...`: got 200 `text/markdown` (no regression) - [x] `curl -sI -H "Accept: application/json" <preview>/changelog/pipelines`: got 406 (prod today returns 200 HTML) - [x] Explicit `.md` fetches for both slug shapes (`/changelog/pipelines.md`, `/changelog/48235-....md`): got 200 `text/markdown` - [x] `curl -sI -H "Accept: text/markdown" <preview>/changelog/does-not-exist-xyz`: got a 404 HTML passthrough from the dynamic route, not a 406 - [x] `pnpm test middleware.test.ts` in `apps/www` at head: 41/41 pass (36 pre-existing + 5 new). No CI job runs the www vitest suite, so this local run is the only oracle for the new tests. ## Linear - fixes GROWTH-1062 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved changelog page handling, including markdown versions of published entries. - Added content negotiation for supported changelog formats, with clear responses for unsupported requests. - **Bug Fixes** - Prevented unpublished numeric-prefix pages from being treated as published. - Fixed deep links under published changelog entries. - **Reliability** - Changelog availability is now detected automatically, with improved validation during content generation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
770f1c2b06 |
fix(aeo): remove ua-based markdown serving (#47770)
## Summary The `ChatGPT-User` live-fetch agent's user-facing reader hard-fails (`(400) OK`) on pages we serve it as markdown via user-agent matching, which made supabase.com blog and product pages unreadable in that assistant. I root-caused this with a controlled fetch diagnostic cross-checked against our request logs: the failing fetches never reach our origin (the failure is cached on their side), pages served as plain HTML read fine everywhere we tested, and the same failure reproduces on other major sites that serve UA-matched markdown, so the reader bug is upstream. This PR removes user-agent-based markdown serving entirely rather than special-casing one agent: UA sniffing is a guess about contractless clients whose fetchers change without notice, and this incident showed the failure mode is silent (we keep serving 200s while the user-facing agent breaks). Markdown remains available on every explicit signal — `Accept: text/markdown` q-value negotiation, explicit `.md` URLs, and llms.txt — which is the same contract-driven model the Claude fetcher already uses successfully (it sends `Accept: text/markdown, text/html, */*` and keeps receiving markdown after this change). ## Changes - Remove the `LLM_USER_AGENT` regex and the `userAgent` parameter from `negotiateMarkdown` in `packages/common/markdown-negotiation.ts`; decisions now depend only on `Accept`, the `.md` suffix, and the markdown-variant manifest - Update both consuming middlewares (`apps/www`, `apps/docs`) to the new signature; no behavior change for Accept-negotiated or `.md` requests - Add the missing `Vary: Accept` header to docs guides-md 200 responses (the www `api-v2/md` route already declares it) - Fix a pre-existing www bug surfaced in review: explicit changelog `.md` URLs rewrote to a doubled `.md.md` path (404) under a markdown-preferring `Accept`, and 406'd on a non-matching `Accept`. The www middleware now strips the `.md` suffix before slug lookup and passes `isMarkdownSuffix` into `negotiateMarkdown`, folding the separate `MD_PAGES` `.md` block into the single negotiation path (same shape as the docs middleware) - Rework tests: UA-independence suites replace the per-agent rewrite tests; a probe Accept header now 406s regardless of user agent (previously agent UAs were exempt); new changelog `.md` negotiation coverage ## Testing Tested locally: - [x] www middleware suite 36/36, docs middleware suite 17/17 - [x] typecheck green for common, www, docs Verified on the Vercel previews (www + docs) with curl: - [x] `ChatGPT-User` and `Claude-User` UA GETs on blog/pricing/guide pages return `text/html` with a default Accept - [x] Claude's real Accept (`text/markdown, text/html, */*`) still returns `text/markdown`; `Accept: text/markdown` and `.md` URLs return `text/markdown`; probe Accept returns 406 - [x] `/changelog/<slug>.md` with `Accept: text/markdown` returns the entry markdown as a direct 200 (production today detours through a 308 to the bare URL); changelog index `.md` and bare-entry Accept negotiation also verified - [x] docs guides markdown 200s carry `Vary: Accept` The intermediate commit (ChatGPT-User-only exclusion) was already verified on the preview: `ChatGPT-User` got HTML while `Accept`/`.md`/other-UA markdown was unaffected. Expected effects post-merge: UA-driven markdown volume in the request logs (~92% of md traffic) collapses to the Accept + `.md` baseline; named-agent page requests return to prerendered/static serving, reversing the extra Vercel function invocations the UA rewrite introduced; user-facing readability in the affected assistant recovers within ~24h as its fetch cache revalidates. The md-share dashboard gets a dated annotation; the ratio is not comparable across this change. ## Linear - fixes GROWTH-973 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Markdown and HTML routing now depends on the request’s `Accept` header and `.md` links, making content negotiation more predictable. * Requests that don’t accept available content now consistently return `406 Not Acceptable`, even for bot-like user agents. * Guide markdown responses now include an `Accept`-based cache variation header to improve correct caching behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
dff4744805 | fix(www): respect Accept q-values and 406 unsupported types (#45394) | ||
|
|
a98a4928b4 | feat(www): rewrite to md for known llm user agents (#45328) | ||
|
|
d409836ca7 |
feat(www,docs): serve marketing pages as .md, advertise via link rel=alternate (#45277)
## Summary Adds `/<page>.md` routes for 10 marketing/product pages (homepage, auth, database, edge-functions, realtime, storage, vector, pricing, modules/cron, modules/queues) so AI agents can fetch clean markdown instead of parsing JS-rendered HTML. Also advertises the markdown alternate via `<link rel="alternate" type="text/markdown">` on marketing and docs pages so agents can discover it. Pricing is generated dynamically via `generatePricingContent()` (single source of truth with `/llms.txt` and `/llms-full.txt`); the other nine slugs are bundled at build time from `content/md/*.md` into a `MD_CONTENT` map. Supersedes #44891 (rebased fresh off current master to avoid a 9-commit replay over rename/rename conflicts created by #44897). ## Changes - New `/api-v2/md/[...slug]` route handler returns the bundled markdown (or dynamic pricing) with `Content-Type: text/markdown`, `X-Content-Type-Options: nosniff`, and appropriate cache headers - Middleware rewrites `/<slug>.md` and `Accept: text/markdown` to the API route for the `MD_PAGES` allowlist; trailing-slash variants (`/auth/`) are normalized so they resolve the same as `/auth` - Build-time codegen `scripts/generateMdContent.mjs` scans `content/md/` and emits `app/api-v2/md/content.generated.ts` exporting both `MD_CONTENT` (Map) and `MD_PAGES` (Set, incl. dynamic `pricing`). Fails the build on slug collision between `content/md/` and `DYNAMIC_SLUGS`. Adding a new marketing `.md` is just dropping a file in `content/md/` (also update `PRODUCT_OVERVIEW_LINKS` in `/llms.txt` since that list is editorial). - 8 permanent redirects `/llms/<product>.txt` → `/<product>.md` so legacy URLs in caches and downstream `llms.txt` copies keep working - `/llms.txt` product overview now references `.md` URLs (incl. `modules/cron`, `modules/queues`); `/llms-full.txt` iterates `MD_CONTENT.values()` (homepage first, then alphabetical) and appends dynamic pricing - `/llms/[slug]` route slimmed to proxy SDK reference files (`js.txt`, `dart.txt`, etc.) since redirects handle product slugs and pricing; pricing branch retained as fallback in case redirects are bypassed - `apps/www/pages/_app.tsx` injects the alternate link conditionally based on `MD_PAGES`; `/pricing` (app router) sets it via page metadata - `apps/docs/app/page.tsx` (the `/docs` root) sets the text/markdown alternate to `/llms-full.txt`; per-guide pages override with their specific `.md` URL via `genGuideMeta` in `GuidesMdx.utils.tsx`. Other docs pages (reference, troubleshooting) inherit nothing. - `apps/www/.vercelignore`: replaces the prior `*.md`/`README.md` rules with `*.md` + `!content/md/**/*.md` so Edge Function READMEs and future scratch `.md` files aren't silently shipped to the build artifact - Drops `apps/www/data/llms/*.txt` and the related `outputFileTracingIncludes` - Test coverage for the new middleware branches: `.md` suffix rewrite (allowlisted vs. fall-through), `Accept: text/markdown` content negotiation, trailing-slash normalization ## Testing (Vercel preview) Local dev server smoke tests passing on `:3771` after each iteration. Re-verified on the preview URL after the latest hardening commit: - [x] `curl -I https://<preview>/llms/auth.txt` — expect `308 Permanent Redirect` to `/auth.md` - [x] `curl https://<preview>/auth.md | head -3` — expect `# Supabase Auth` - [x] `curl https://<preview>/pricing.md | head -3` — expect `# Supabase Pricing` with current tier values - [x] `curl https://<preview>/modules/cron.md | head -3` — expect `# Supabase Cron` - [x] `curl -H 'Accept: text/markdown' https://<preview>/ | head -3` — expect `# Supabase` (homepage.md) - [x] `curl https://<preview>/llms.txt` — Product Overview section lists `.md` URLs and includes Cron + Queues - [x] `curl https://<preview>/llms-full.txt | grep -E '^# Supabase (Cron\|Queues\|Pricing)'` — Cron and Pricing each match once; Queues matches twice (marketing module + existing docs guide) - [x] View source on `/`, `/pricing`, `/database` — expect `<link rel="alternate" type="text/markdown" href="/<slug>.md">` - [x] View source on `/docs` — expect `<link rel="alternate" type="text/markdown" href="/llms-full.txt">` - [x] View source on a docs guide page (e.g., `/docs/guides/auth`) — expect per-guide `.md` alternate; reference/troubleshooting pages should NOT emit a markdown alternate - [x] `curl -I https://<preview>/auth.md` — expect `X-Content-Type-Options: nosniff` - [x] `curl -I -L -H 'Accept: text/markdown' https://<preview>/auth/` — should resolve to markdown content (trailing-slash normalization, with Vercel's auto-redirect) ## Linear - fixes GROWTH-760 ## Follow-up (separate PR) GROWTH-760 also asks about extending `.md` to blog/customers/events. Different mechanism (path-prefix middleware, MDX read at request time via `gray-matter`) so it deserves its own review. Will open a follow-up PR after this lands. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Serve prebuilt and dynamic Markdown docs via new markdown endpoints and routing; pages now advertise markdown alternates (including pricing). * Added Cron and Queues module documentation pages. * **Documentation** * Minor formatting tweaks to Realtime and Storage docs. * **Chores** * Added build-time Markdown content generation and adjusted ignore/deploy rules for generated files. * Added redirects from legacy text-based product URLs to new markdown pages. * **Tests** * Expanded tests for markdown routing and content-negotiation behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b041ebfaa5 | feat(www): Phase 2 — enable cookie stamping on /dashboard and /docs paths (#43677) | ||
|
|
8ebbad3a5b |
feat(growth): expand www middleware to /dashboard and /docs (Phase 1 - instrumentation only) (#43413)
## Problem The `_sb_first_referrer` cookie isn't working. The www middleware matcher explicitly excludes `/dashboard` and `/docs`, so the cookie never gets stamped for Studio or Docs traffic. PostHog confirmed: only 1 event with `first_referrer_cookie_present=true` out of ~46.5M Studio pageviews in the last 7 days. ## Background: what the matcher does In Next.js, the `matcher` config controls which incoming requests the middleware function even runs on. If a path doesn't match, the middleware is skipped entirely — the request passes through untouched. If it matches, the middleware runs and can mutate the response (set cookies, headers, etc.). This matters because Studio's SPA navigation works via silent `/_next/data/` JSON fetches. If middleware runs on those requests and returns `NextResponse.next()` with any mutations, it breaks those fetches and causes full page reloads instead of client-side transitions. ## What we tried before | PR | www runs on `/dashboard`? | Studio `proxy.ts` runs on all routes? | Result | |---|---|---|---| | **#42768** (Attempt 1) | ✅ Yes — and also intercepts `_next/data` | ✅ Yes — `matcher` config removed, stamps cookie everywhere | Full page reloads in Studio | | **#43129** (Full revert) | ❌ No — www middleware deleted entirely | ❌ No — restored to `matcher: '/api/*'` only | Back to baseline, no cookie stamping anywhere | | **#43153** (Attempt 2) | ❌ No — `/dashboard` explicitly excluded | ✅ Yes — `matcher` config removed again, stamps cookie everywhere | Full page reloads in Studio again | | **#43189** (Attempt 3) | ❌ No — same as #43153 | ✅ Yes — `matcher` config still removed, cookie stamping made conditional | Still broken | | **#43190** (Ivan's fix) | ❌ No — `/dashboard` still excluded | ❌ No — restored to `matcher: '/api/*'` only | Works — but cookie never stamps for `/dashboard` traffic | | **#43413** (this PR) | ✅ Yes — sets diagnostic cookie only, no attribution stamping yet | ❌ No — unchanged, still `matcher: '/api/*'` only | ❓ Untested in prod | The common factor in every failure: Studio's `proxy.ts` ran on all routes (including `_next/data` requests), which broke SPA navigation. This PR is the first one that runs www middleware on `/dashboard` while Studio's `proxy.ts` stays in its original narrow `/api/*` scope. ## What changed This is Phase 1 of a two-phase rollout. We remove `dashboard|docs` from the matcher's negative lookahead so www middleware runs on those paths — but instead of stamping cookies, we set a short-lived (60s) diagnostic cookie `_sb_mw_diag` on `/dashboard` and `/docs` requests. The diagnostic cookie encodes `hit=1&would_stamp={0|1}&has_cookie={0|1}`, which Studio telemetry reads on the initial pageview and reports to PostHog as `mw_diag_hit`, `mw_diag_would_stamp`, and `mw_diag_has_existing_cookie` properties. A cookie rather than a header because response headers aren't readable by JS. It also tests the actual Set-Cookie mutation path that Phase 2 will use (which is what Next.js issue #41885 is specifically about). Phase 1 answers two key questions before we commit to Phase 2: 1. Does expanding the matcher break Studio SPA navigation? 2. What % of /dashboard arrivals would get a first-referrer cookie stamped in Phase 2? ## Phase 2 readiness criteria **Important caveat**: `mw_diag_*` data reflects consented users only and may under-represent first-visit anonymous traffic. The Phase 2 decision should account for this — the actual middleware execution rate is likely higher than what PostHog reports. ### PostHog query spec **Middleware execution rate**: Of all Studio initial pageviews on `/dashboard` or `/docs` paths, what percentage have `mw_diag_hit = true`? Expected: >= 90%. Below 70% warrants investigation (could indicate edge caching bypassing middleware, or a matcher configuration issue). ``` Filter: event = "$pageview" AND (current_url contains "/dashboard" OR current_url contains "/docs") Breakdown: mw_diag_hit (true vs null/missing) Metric: count(mw_diag_hit = true) / count(all) * 100 ``` **Would-stamp rate**: Of events with `mw_diag_hit = true`, what percentage have `mw_diag_would_stamp = true`? This tells us what percentage of Phase 2 traffic would actually get a cookie stamped. No hard threshold — unexpected values (< 5% or > 95%) suggest a logic bug worth investigating before Phase 2. ``` Filter: event = "$pageview" AND mw_diag_hit = true Breakdown: mw_diag_would_stamp (true vs false) Metric: count(mw_diag_would_stamp = true) / count(all) * 100 ``` **Existing cookie rate**: Of events with `mw_diag_hit = true`, what percentage have `mw_diag_has_existing_cookie = true`? This tells us how many users already have the cookie from a prior www visit. ``` Filter: event = "$pageview" AND mw_diag_hit = true Breakdown: mw_diag_has_existing_cookie (true vs false) Metric: count(mw_diag_has_existing_cookie = true) / count(all) * 100 ``` ### Go / no-go threshold table | Signal | Go | Investigate | No-Go | |---|---|---|---| | `mw_diag_hit` rate (% of /dashboard+/docs pageviews) | >= 90% | 70-90% | < 70% | | SPA navigation errors (Sentry / Vercel logs) | No increase | < 0.1% increase | > 0.5% increase | | Middleware p99 latency (Vercel function logs) | < 50ms added | 50-100ms | > 100ms | | Sample volume in first 24h | > 1,000 events | 100-1,000 (extend window) | < 100 (insufficient data) | ## Changes - `apps/www/middleware.ts`: Removed `dashboard|docs` from matcher; added `isDashboardOrDocs` guard that sets `_sb_mw_diag` diagnostic cookie instead of stamping attribution - `apps/www/middleware.test.ts`: 12 tests covering cookie stamping on www paths, diagnostic cookie encoding for all scenarios (external referrer, direct nav, internal referrer, existing cookie) - `packages/common/first-referrer-cookie.ts`: Exported `MW_DIAG_COOKIE_NAME`, `MwDiagData` type, and `parseMwDiagCookie()` helper - `packages/common/telemetry.tsx`: Reads `_sb_mw_diag` on initial Studio pageview; reports `mw_diag_*` properties to PostHog ## Testing Unit tests pass (13/13 www, 31/31 first-referrer-cookie). Production validation needed: - [ ] Studio SPA navigation works (tab changes, SQL editor, no full page reloads) - [ ] PostHog shows `mw_diag_hit = true` on Studio initial pageviews - [ ] `mw_diag_would_stamp` distribution looks reasonable before enabling Phase 2 - [ ] Monitor 24h before Phase 2 Ref: GROWTH-625 / GROWTH-668 |