From c7dff7b5443d7504460d843a6e0519db22c2bcc4 Mon Sep 17 00:00:00 2001 From: CharlieHelps Date: Sun, 10 Aug 2025 23:35:50 +0000 Subject: [PATCH 1/3] feat(netlify): add pre-mount nf_ab query-param cookie setter for Split Testing; docs for opt-in/opt-out and limitations --- README.md | 4 +++ app/root.tsx | 57 +++++++++++++++++++++++++++++++++++++++++++ docs/split-testing.md | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 docs/split-testing.md diff --git a/README.md b/README.md index 834ee3f9a..d6053a571 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ Create beautiful, interactive mini apps with zero setup. Your creations are auto - Add your AI account key from [OpenRouter](https://openrouter.ai/settings/keys) - Run `pnpm dev` +## Developer previews on the main domain (no redirects) + +Opt into an experimental branch deploy on the primary site using Netlify Split Testing with a cookie. See [docs/split-testing.md](docs/split-testing.md) for usage and limitations. + ## Your Work is Always Safe Every app you create is automatically saved, so you can: diff --git a/app/root.tsx b/app/root.tsx index 4837ce766..c6f7220bd 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -86,6 +86,63 @@ export function Layout({ children }: { children: React.ReactNode }) { + {/** + * Netlify Split Testing opt-in/out via query params (pre-mount) + * + * Supports setting or clearing the `nf_ab` cookie before the app initializes + * so Netlify can route this request (and subsequent ones) to a specific + * branch deploy on the primary domain without redirecting. This preserves + * origin-scoped storage like localStorage. + * + * Usage examples (open on the main domain): + * - Set to a branch: ?nf_ab=my-experimental-branch + * - Clear the cookie: ?nf_ab=clear (aliases: off, reset, none) + * - Also accepts `ab=` as a synonym for `nf_ab=` + */} + diff --git a/docs/split-testing.md b/docs/split-testing.md index 1183cc4cf..875982db6 100644 --- a/docs/split-testing.md +++ b/docs/split-testing.md @@ -38,6 +38,12 @@ This removes the cookie and reloads once. Without the cookie, Netlify serves the - When Split Testing is enabled for a site, Netlify does not execute Edge Functions for that site. If you rely on Edge Functions, enable Split Testing only when acceptable for your routes. - Storage schema compatibility: both branches share the same origin storage. If you change localStorage structure on one branch, ensure the other can tolerate or migrate it. +#### Cookie scope (apex vs subdomains) + +By default, the `nf_ab` cookie is set without a `Domain` attribute. That means it is host‑scoped and will not be shared across hosts like `vibes.diy` and `www.vibes.diy`. If you need the same selection to apply across subdomains, consider setting a cookie `Domain` (for example, `Domain=.vibes.diy`). Doing so shares the bucket across all subdomains but also broadens the cookie’s reach. Evaluate privacy and collision trade‑offs before enabling. + +This repository ships the default host‑scoped behavior. If cross‑subdomain behavior is desired, we can add an option to include a `Domain` attribute in the cookie setter; please confirm the desired scope on the PR. + ### Configuration required in Netlify Split Testing is configured at the site level in the Netlify UI. Include your production branch (e.g., `main`) and at least one experimental branch deploy. Do not add PR Deploy Previews. Traffic allocation can be 0/100 — the cookie opt‑in will still work. diff --git a/public/nf-ab.cookie.js b/public/nf-ab.cookie.js new file mode 100644 index 000000000..839e2e9c9 --- /dev/null +++ b/public/nf-ab.cookie.js @@ -0,0 +1,71 @@ +/* + * Netlify Split Testing opt-in/out via query params (pre-mount) + * + * Reads `?nf_ab` or `?ab` on arrival to set/clear the `nf_ab` cookie before the app + * initializes, so Netlify can route this and subsequent requests to a specific branch + * deploy on the primary domain without redirects. Preserves origin-scoped storage. + * + * Usage examples (open on the main domain): + * - Set to a branch: ?nf_ab=my-experimental-branch + * - Clear the cookie: ?nf_ab=clear (aliases: off, reset, none) + * - Also accepts `ab=` as a synonym for `nf_ab=` + */ +(function () { + try { + var u = new URL(window.location.href); + var sp = u.searchParams; + var hasNf = sp.has('nf_ab'); + var hasAb = sp.has('ab'); + if (!hasNf && !hasAb) return; + + var value = hasNf ? sp.get('nf_ab') : sp.get('ab'); + var clear = value && /^(clear|off|reset|none)$/i.test(value); + + var secure = window.location.protocol === 'https:' ? '; Secure' : ''; + var cookieBase = 'nf_ab='; + + // Make whitespace after semicolons optional to be robust across browsers + var currentMatch = document.cookie.match(/(?:^|;\s*)nf_ab=([^;]+)/); + var current = currentMatch ? decodeURIComponent(currentMatch[1]) : null; + + // Apply changes if needed + if (clear) { + // Clear cookie if present + if (current) { + document.cookie = 'nf_ab=; Max-Age=0; Path=/; SameSite=Lax' + secure; + } + } else if (value && value !== current) { + // Long-ish expiration so the choice sticks for developers + var expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString(); + document.cookie = + cookieBase + + encodeURIComponent(value) + + '; Expires=' + + expires + + '; Path=/; SameSite=Lax' + + secure; + } + + // Determine whether a reload is required (only if the effective value changed) + var shouldReload = false; + if (clear) { + if (current) shouldReload = true; // only reload if we actually removed an existing cookie + } else if (value && value !== current) { + shouldReload = true; + } + + // Always remove our params to avoid loops and keep URLs clean + sp.delete('nf_ab'); + sp.delete('ab'); + var newUrl = u.origin + u.pathname + (sp.toString() ? '?' + sp.toString() : '') + u.hash; + + if (shouldReload) { + window.location.replace(newUrl); + } else if (hasNf || hasAb) { + // Clean the URL without a reload if nothing changed + history.replaceState(null, '', newUrl); + } + } catch (_e) { + // Swallow errors to avoid blocking page load in edge cases + } +})(); From 970e212e0608b93ecf63f9d10bf769b13bd2b976 Mon Sep 17 00:00:00 2001 From: CharlieHelps Date: Mon, 11 Aug 2025 00:41:06 +0000 Subject: [PATCH 3/3] docs(split-testing): document only `ab=`; remove `?nf_ab` from user-facing docs --- README.md | 8 +++++++- docs/split-testing.md | 25 +++++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 779e2e756..c7d9fcf10 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,13 @@ Create beautiful, interactive mini apps with zero setup. Your creations are auto ## Developer previews on the main domain (no redirects) -Opt into an experimental branch deploy on the primary site using Netlify Split Testing with a cookie. Note: the `nf_ab` cookie is host‑scoped by default (not shared between `www` and apex). See [docs/split-testing.md](docs/split-testing.md) for details, usage, and scope options. +Opt into an experimental branch deploy on the primary site using Netlify Split Testing with a cookie. Use `?ab=` on the main domain, for example: + +``` +https://vibes.diy/?ab=feature-new-ui +``` + +Note: the underlying Netlify cookie is named `nf_ab` and is host‑scoped by default (not shared between `www` and apex). See [docs/split-testing.md](docs/split-testing.md) for details and scope options. ## Your Work is Always Safe diff --git a/docs/split-testing.md b/docs/split-testing.md index 875982db6..a5e3aec20 100644 --- a/docs/split-testing.md +++ b/docs/split-testing.md @@ -6,35 +6,33 @@ This app supports Netlify Split Testing so developers can opt into an alternate - Netlify routes traffic between eligible branch deploys at the CDN proxy layer. - Visitors are bucketed by the `nf_ab` cookie. When present, Netlify serves all content for that visitor from the selected branch deploy on the main domain. -- We ship a tiny pre‑mount script that reads query parameters on arrival and sets/clears the `nf_ab` cookie before the app initializes, then reloads once so the CDN picks up the selection. +- We ship a tiny pre‑mount script that reads the `ab=` query parameter on arrival and sets/clears the `nf_ab` cookie before the app initializes, then reloads once so the CDN picks up the selection. ### Opt in -Open the primary site URL with one of the following query parameters: +Open the primary site URL with: -- `?nf_ab=` — sets the cookie to the exact branch name and reloads. -- `?ab=` — synonym for convenience. +- `?ab=` — sets the cookie to the exact branch name and reloads. Example: ``` -https://vibes.diy/?nf_ab=feature-new-ui +https://vibes.diy/?ab=feature-new-ui ``` After reload, Netlify will proxy all requests for that browser to the `feature-new-ui` branch deploy at `https://vibes.diy/...` (no redirect). ### Opt out / revert -Open the primary site URL with any of the following to clear the bucket: +Open the primary site URL with the following to clear the bucket: -- `?nf_ab=clear` (aliases: `off`, `reset`, `none`) -- `?ab=clear` +- `?ab=clear` (aliases: `off`, `reset`, `none`) -This removes the cookie and reloads once. Without the cookie, Netlify serves the primary branch (e.g., `main`). If you want to force the primary branch explicitly, you can also set `?nf_ab=main` (or your production branch name). +This removes the cookie and reloads once. Without the cookie, Netlify serves the primary branch (e.g., `main`). If you want to force the primary branch explicitly, you can also set `?ab=main` (or your production branch name). ### Important limitations -- Split Testing only supports persistent branch deploys. Deploy Previews (per‑PR) cannot be targeted by `nf_ab`. +- Split Testing only supports persistent branch deploys. Deploy Previews (per‑PR) cannot be targeted by the cookie‑based Split Testing mechanism (`nf_ab`). - When Split Testing is enabled for a site, Netlify does not execute Edge Functions for that site. If you rely on Edge Functions, enable Split Testing only when acceptable for your routes. - Storage schema compatibility: both branches share the same origin storage. If you change localStorage structure on one branch, ensure the other can tolerate or migrate it. @@ -48,9 +46,4 @@ This repository ships the default host‑scoped behavior. If cross‑subdomain b Split Testing is configured at the site level in the Netlify UI. Include your production branch (e.g., `main`) and at least one experimental branch deploy. Do not add PR Deploy Previews. Traffic allocation can be 0/100 — the cookie opt‑in will still work. -### Open questions - -- Which branch deploy(s) should be eligible for opt‑in right now? -- Do we want different query parameter names or additional aliases? - -Edit this doc once those choices are finalized. +