diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 9da9859f..1ae0daac 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -11,9 +11,11 @@ on:
branches: [main]
types: [opened, synchronize, reopened]
+# TODO: Shard tests across a matrix to speed up CI.
+# See: https://playwright.dev/docs/test-sharding#github-actions-example
jobs:
test:
- timeout-minutes: 10
+ timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- name: Harden Runner
diff --git a/src/components/FrameworkLinks.astro b/src/components/FrameworkLinks.astro
new file mode 100644
index 00000000..c475469a
--- /dev/null
+++ b/src/components/FrameworkLinks.astro
@@ -0,0 +1,23 @@
+---
+/**
+ * FrameworkLinks — Astro wrapper
+ *
+ * On SDK-scoped routes (`/sdk/:sdk/...`), renders nothing — the SDK switcher
+ * in the table of contents already provides navigation between SDK variants.
+ *
+ * On legacy (unscoped) routes, delegates to the React `FrameworkLinksReact`
+ * component with `client:load` so the existing `?f=` link grid stays the same.
+ */
+import FrameworkLinksReact from "@/components/FrameworkLinksReact";
+import { sdkFromPathname } from "@/lib/sdk";
+
+const sdkKey = sdkFromPathname(Astro.url.pathname);
+---
+
+{
+ sdkKey ? null : (
+
+
+
+ )
+}
diff --git a/src/components/FrameworkLinks.tsx b/src/components/FrameworkLinksReact.tsx
similarity index 100%
rename from src/components/FrameworkLinks.tsx
rename to src/components/FrameworkLinksReact.tsx
diff --git a/src/components/SdkReferenceLinkByFramework.astro b/src/components/SdkReferenceLinkByFramework.astro
index a5c6b87a..b1f99836 100644
--- a/src/components/SdkReferenceLinkByFramework.astro
+++ b/src/components/SdkReferenceLinkByFramework.astro
@@ -1,9 +1,9 @@
---
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { Link } from "@/components/link";
---
-
+
+ ) : (
+
+
+
+ )
+ ) : (
+
+ {Astro.slots.has("default") && (
+
+ )}
+ {Astro.slots.has("astro") && (
+
+ )}
+ {Astro.slots.has("bun") && (
+
+ )}
+ {Astro.slots.has("bun-hono") && (
+
+ )}
+ {Astro.slots.has("deno") && (
+
+ )}
+ {Astro.slots.has("fastify") && (
+
+ )}
+ {Astro.slots.has("nest-js") && (
+
+ )}
+ {Astro.slots.has("next-js") && (
+
+ )}
+ {Astro.slots.has("node-js") && (
+
+ )}
+ {Astro.slots.has("node-js-express") && (
+
+ )}
+ {Astro.slots.has("node-js-hono") && (
+
+ )}
+ {Astro.slots.has("nuxt") && (
+
+ )}
+ {Astro.slots.has("python-fastapi") && (
+
+ )}
+ {Astro.slots.has("python-flask") && (
+
+ )}
+ {Astro.slots.has("react-router") && (
+
+ )}
+ {Astro.slots.has("remix") && (
+
+ )}
+ {Astro.slots.has("sveltekit") && (
+
+ )}
+
+ )
+}
diff --git a/src/components/SlotByFramework.tsx b/src/components/SlotByFrameworkReact.tsx
similarity index 100%
rename from src/components/SlotByFramework.tsx
rename to src/components/SlotByFrameworkReact.tsx
diff --git a/src/components/TextByFramework.astro b/src/components/TextByFramework.astro
new file mode 100644
index 00000000..cecefacb
--- /dev/null
+++ b/src/components/TextByFramework.astro
@@ -0,0 +1,49 @@
+---
+/**
+ * TextByFramework — Astro wrapper
+ *
+ * On SDK-scoped routes (`/sdk/:sdk/...`), renders the matching text prop at
+ * build time with zero client JavaScript.
+ *
+ * On legacy (unscoped) routes, delegates to the React `TextByFrameworkReact`
+ * component with `client:load` so the existing client-side framework switching
+ * continues to work.
+ */
+import type { FrameworkKey } from "@/lib/prefs";
+import TextByFrameworkReact from "@/components/TextByFrameworkReact";
+import { sdkFromPathname, legacyKeyFromPathname } from "@/lib/sdk";
+import { kebabToCamel } from "@/lib/utils";
+
+type Props = { [key in FrameworkKey]?: string } & {
+ /** Fallback text when no framework-specific prop matches. */
+ default?: string;
+};
+
+const props = Astro.props as Props;
+const sdkKey = sdkFromPathname(Astro.url.pathname);
+
+let resolvedText: string | undefined;
+if (sdkKey) {
+ const legacyKey = legacyKeyFromPathname(Astro.url.pathname);
+ if (legacyKey) {
+ const camelKey = kebabToCamel(legacyKey) as FrameworkKey;
+ if (props[legacyKey] !== undefined) {
+ resolvedText = props[legacyKey];
+ } else if (props[camelKey] !== undefined) {
+ resolvedText = props[camelKey];
+ } else {
+ resolvedText = props["default"];
+ }
+ }
+}
+---
+
+{
+ sdkKey && resolvedText !== undefined ? (
+
+ ) : (
+
+
+
+ )
+}
diff --git a/src/components/TextByFramework.tsx b/src/components/TextByFrameworkReact.tsx
similarity index 100%
rename from src/components/TextByFramework.tsx
rename to src/components/TextByFrameworkReact.tsx
diff --git a/src/components/TitleByFramework.tsx b/src/components/TitleByFramework.tsx
index 4a77c9fb..1af0ca44 100644
--- a/src/components/TitleByFramework.tsx
+++ b/src/components/TitleByFramework.tsx
@@ -1,5 +1,5 @@
-import type { Props as TextByFrameworkProps } from "@/components/TextByFramework";
-import TextByFramework from "@/components/TextByFramework";
+import type { Props as TextByFrameworkProps } from "@/components/TextByFrameworkReact";
+import TextByFramework from "@/components/TextByFrameworkReact";
import type { FrameworkKey } from "@/lib/prefs";
import type { ForwardedRef, PropsWithChildren } from "react";
import { forwardRef, useCallback, useEffect, useState } from "react";
diff --git a/src/content/docs/bot-protection/quick-start.mdx b/src/content/docs/bot-protection/quick-start.mdx
index 37432dec..2e34c440 100644
--- a/src/content/docs/bot-protection/quick-start.mdx
+++ b/src/content/docs/bot-protection/quick-start.mdx
@@ -52,10 +52,10 @@ import { CardGrid } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
import { frameworks } from "@/lib/prefs";
import WhatIsArcjet from "/src/components/WhatIsArcjet.astro";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import FrameworkName from "@/components/FrameworkName";
import FAQs from "/src/components/FAQs.astro";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import Comments from "/src/components/Comments.astro";
import SdkReferenceLinkByFramework from "/src/components/SdkReferenceLinkByFramework.astro";
@@ -109,7 +109,6 @@ Arcjet bot detection allows you to manage traffic by automated clients and bots.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -125,7 +124,7 @@ application from bots and automated clients.
In your project root, run the following command to install the SDK:
-
+
@@ -142,7 +141,7 @@ In your project root, run the following command to install the SDK:
[Create a free Arcjet account](https://app.arcjet.com) then follow the
instructions to add a site and get a key.
-
+
@@ -156,7 +155,7 @@ instructions to add a site and get a key.
### 3. Add bot protection
-
+
@@ -168,7 +167,7 @@ instructions to add a site and get a key.
-
+
diff --git a/src/content/docs/bot-protection/reference.mdx b/src/content/docs/bot-protection/reference.mdx
index 8adff610..2fea82f4 100644
--- a/src/content/docs/bot-protection/reference.mdx
+++ b/src/content/docs/bot-protection/reference.mdx
@@ -90,9 +90,9 @@ import Business from "@/components/badges/Business.astro";
import Enterprise from "@/components/badges/Enterprise.astro";
import Comments from "@/components/Comments.astro";
import { Link } from "@/components/link";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import { frameworks } from "@/lib/prefs";
import BunAllowingBots from "@/snippets/bot-protection/reference/bun/AllowingBots.mdx";
@@ -154,7 +154,6 @@ import SvelteKitPerRouteVsHooks from "@/snippets/bot-protection/reference/svelte
Arcjet bot detection allows you to manage traffic by automated clients and bots.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -208,7 +207,7 @@ This behavior is configured with an `allow` list from our [full list of
bots](https://arcjet.com/bot-list) and/or
bot categories.
-
+
@@ -228,7 +227,7 @@ This behavior is configured with a `deny` list from our [full list of
bots](https://arcjet.com/bot-list) and/or
bot categories.
-
+
@@ -238,7 +237,7 @@ bots](https://arcjet.com/bot-list) and/or
-
+
@@ -255,7 +254,7 @@ Arcjet provides a single `protect` function that is used to execute your
protection rules. This requires a `request` argument which is the request
context as passed to the request handler.
-
+
@@ -288,7 +287,7 @@ for (const result of decision.results) {
}
```
-
+
@@ -306,7 +305,7 @@ request. A request may be identified as zero, one, or more bots/categories-all
of which will be available on the `decision.allowed` and `decision.denied`
properties.
-
+
@@ -339,7 +338,7 @@ header.
See an example of how to do this.
:::
-
+
@@ -355,7 +354,7 @@ All categories are also provided as enumerations, which allows for programmatic
access. For example, you may want to allow most of `CATEGORY:GOOGLE` except
their "advertising quality" bot.
-
+
@@ -497,7 +496,7 @@ Arcjet can also be triggered based using a sample of your traffic.
See the Testing section of the docs for details.
-
+
diff --git a/src/content/docs/email-validation/quick-start.mdx b/src/content/docs/email-validation/quick-start.mdx
index 01516034..ee12d132 100644
--- a/src/content/docs/email-validation/quick-start.mdx
+++ b/src/content/docs/email-validation/quick-start.mdx
@@ -44,8 +44,8 @@ import { Link } from "@/components/link";
import { CardGrid } from "@astrojs/starlight/components";
import SdkReferenceLinkByFramework from "/src/components/SdkReferenceLinkByFramework.astro";
import WhatIsArcjet from "/src/components/WhatIsArcjet.astro";
-import FrameworkLinks from "@/components/FrameworkLinks";
-import SlotByFramework from "@/components/SlotByFramework";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import FrameworkName from "@/components/FrameworkName";
import { frameworks } from "@/lib/prefs";
import Step1Bun from "@/snippets/email-validation/quick-start/bun/Step1.mdx";
@@ -78,7 +78,6 @@ reduce the amount of spam or fraudulent accounts.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -94,7 +93,7 @@ your app.
In your project root, run the following command to install the SDK:
-
+
@@ -109,7 +108,7 @@ In your project root, run the following command to install the SDK:
instructions to add a site and get a key. Add it to a `.env.local` file in your
project root.
-
+
@@ -124,7 +123,7 @@ The example below shows how to use Arcjet to check an email address. If the
email address is invalid or if no MX records are configured, Arcjet will return
a deny decision.
-
+
@@ -133,7 +132,7 @@ a deny decision.
-
+
diff --git a/src/content/docs/email-validation/reference.mdx b/src/content/docs/email-validation/reference.mdx
index 989c3ba8..9aa38e7e 100644
--- a/src/content/docs/email-validation/reference.mdx
+++ b/src/content/docs/email-validation/reference.mdx
@@ -24,9 +24,9 @@ ajToc:
import { Link } from "@/components/link";
import { Aside, Badge } from "@astrojs/starlight/components";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import DisplayType from "@/components/DisplayType.astro";
import { frameworks } from "@/lib/prefs";
@@ -58,7 +58,6 @@ preventing users from signing up with fake email addresses and can significantly
reduce the amount of spam or fraudulent accounts.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -188,7 +187,7 @@ to allow certain types of email addresses, you can modify the options:
literals. Defaults to `false`. Changing to `true` means that
`foo@[123.456.789.0]` would be allowed.
-
+
@@ -199,7 +198,7 @@ protection rules. This requires a `request` argument which is the request
context as passed to the request handler. When configured with a `validateEmail`
rule it also requires an additional `email` prop.
-
+
@@ -234,7 +233,7 @@ for (const result of decision.results) {
This example will log the full result as well as the email validation rule:
-
+
@@ -248,7 +247,7 @@ This example will log the full result as well as the email validation rule:
In addition to being able to deny specific email types, you can also configure
Arcjet to only allow specific email types and all other types will be blocked.
-
+
@@ -306,7 +305,7 @@ error result for more information.
If all other rules that were run returned an `ALLOW` result, then the final Arcjet
conclusion will be `ERROR`.
-
+
diff --git a/src/content/docs/filters/quick-start.mdx b/src/content/docs/filters/quick-start.mdx
index 80c149bc..a3c739c6 100644
--- a/src/content/docs/filters/quick-start.mdx
+++ b/src/content/docs/filters/quick-start.mdx
@@ -56,8 +56,8 @@ import Comments from "/src/components/Comments.astro";
import Faqs from "/src/components/FAQs.astro";
import SdkReferenceLinkByFramework from "/src/components/SdkReferenceLinkByFramework.astro";
import WhatIsArcjet from "/src/components/WhatIsArcjet.astro";
-import SlotByFramework from "@/components/SlotByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import FrameworkName from "@/components/FrameworkName";
import { frameworks } from "@/lib/prefs";
import { Link } from "@/components/link";
@@ -112,7 +112,6 @@ allows you to quickly enforce rules like allow/deny by country, network, or
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -128,7 +127,7 @@ filters.
Run the following command to install the SDK in your project:
-
+
@@ -146,7 +145,7 @@ Run the following command to install the SDK in your project:
[Create a free Arcjet account](https://app.arcjet.com) and follow the
instructions to add a site and get a key.
-
+
@@ -161,7 +160,7 @@ instructions to add a site and get a key.
### 3. Add filters
-
+
@@ -176,7 +175,7 @@ instructions to add a site and get a key.
{/* Note: Step 4 has its heading inside it as it is dynamic. Make sure that the table of contents (in frontmatter) reflects that dynamic heading correctly. */}
-
+
@@ -193,7 +192,7 @@ instructions to add a site and get a key.
Make a `curl` request from your terminal to your app:
-
+
```sh
diff --git a/src/content/docs/filters/reference.mdx b/src/content/docs/filters/reference.mdx
index 836b29c4..7bcbd2da 100644
--- a/src/content/docs/filters/reference.mdx
+++ b/src/content/docs/filters/reference.mdx
@@ -38,9 +38,9 @@ ajToc:
import { Link } from "@/components/link";
import { Badge } from "@astrojs/starlight/components";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import DisplayType from "@/components/DisplayType.astro";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { frameworks } from "@/lib/prefs";
import BunAllow from "@/snippets/filters/reference/bun/Allow.mdx";
import DenoAllow from "@/snippets/filters/reference/deno/Allow.mdx";
@@ -58,7 +58,6 @@ allows you to quickly enforce rules like allow/deny by country, network, or
`user-agent`.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -93,7 +92,7 @@ See [decision](#decision) below for details of examining the execution results.
In this example, the expression matches non-VPN GET requests from the US.
Requests matching the expression are allowed and all others are denied.
-
+
diff --git a/src/content/docs/get-started.mdx b/src/content/docs/get-started.mdx
index eae2e67a..20259334 100644
--- a/src/content/docs/get-started.mdx
+++ b/src/content/docs/get-started.mdx
@@ -58,8 +58,8 @@ ajToc:
---
import { YouTube } from "astro-embed";
-import SlotByFramework from "@/components/SlotByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import { Link } from "@/components/link";
import { LinkButton, CardGrid } from "@astrojs/starlight/components";
import FAQs from "/src/components/FAQs.astro";
@@ -168,17 +168,17 @@ protection. Data redaction. A developer-first approach to security.
This guide will show you how to set up a simple API server protected by Arcjet.
-
+
-
+
## 1. Install Arcjet
In your project root, run the following:
-
+
@@ -199,7 +199,7 @@ In your project root, run the following:
### Requirements
-
+
@@ -223,7 +223,7 @@ In your project root, run the following:
[Create a free Arcjet account](https://app.arcjet.com) then follow the
instructions to add a site and get a key.
-
+
@@ -247,7 +247,7 @@ instructions to add a site and get a key.
This configures Arcjet rules to protect your app from attacks, apply a rate
limit, and prevent bots from accessing your app.
-
+
@@ -266,7 +266,7 @@ limit, and prevent bots from accessing your app.
-
+
diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx
index 83b63d3f..e9b23522 100644
--- a/src/content/docs/index.mdx
+++ b/src/content/docs/index.mdx
@@ -14,14 +14,13 @@ hero:
---
import { YouTube } from "astro-embed";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import Launch from "@/components/Launch";
## Learn more
diff --git a/src/content/docs/nosecone/quick-start.mdx b/src/content/docs/nosecone/quick-start.mdx
index 317c5af7..1a0c5103 100644
--- a/src/content/docs/nosecone/quick-start.mdx
+++ b/src/content/docs/nosecone/quick-start.mdx
@@ -39,8 +39,8 @@ ajToc:
import WhatAreArcjetUtilities from "@/components/WhatAreArcjetUtilities.astro";
import FrameworkName from "@/components/FrameworkName";
import NpmSdkVersion from "@/components/NpmSdkVersion.astro";
-import FrameworkLinks from "@/components/FrameworkLinks";
-import SlotByFramework from "@/components/SlotByFramework";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { frameworks } from "@/lib/prefs";
import { CardGrid } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
@@ -103,7 +103,6 @@ See the reference guide for
full details on each option.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -126,7 +125,7 @@ default security headers.
In your project root, run the following command to install the Arcjet Nosecone
library for your framework:
-
+
@@ -136,7 +135,7 @@ library for your framework:
### 2. Configure your application
-
+
@@ -146,7 +145,7 @@ library for your framework:
### 3. Run your application
-
+
@@ -162,7 +161,7 @@ application, but may break things (particularly the CSP header).
We recommend you test your application thoroughly and tweak the settings to
ensure it continues to work as expected.
-
+
diff --git a/src/content/docs/rate-limiting/quick-start.mdx b/src/content/docs/rate-limiting/quick-start.mdx
index 5ffdc244..e49eb816 100644
--- a/src/content/docs/rate-limiting/quick-start.mdx
+++ b/src/content/docs/rate-limiting/quick-start.mdx
@@ -46,9 +46,9 @@ ajToc:
import { Link } from "@/components/link";
import { CardGrid } from "@astrojs/starlight/components";
import WhatIsArcjet from "@/components/WhatIsArcjet.astro";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import FrameworkName from "@/components/FrameworkName";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import FAQs from "@/components/FAQs.astro";
import Comments from "@/components/Comments.astro";
import { frameworks } from "@/lib/prefs";
@@ -91,14 +91,13 @@ requests a client can make over a period of time.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
title="Choose a framework"
/>
-
+
@@ -110,7 +109,7 @@ This guide will show you how to add a simple rate limit to your
+
@@ -124,7 +123,7 @@ In your project root, run the following command to install the SDK:
[Create a free Arcjet account](https://app.arcjet.com) then follow the
instructions to add a site and get a key.
-
+
@@ -140,7 +139,7 @@ identify the user based on their ID e.g. if they are logged in. The bucket is
configured with a maximum capacity of 10 tokens and refills by 5 tokens every
10 seconds. Each request consumes 5 tokens.
-
+
@@ -149,7 +148,7 @@ configured with a maximum capacity of 10 tokens and refills by 5 tokens every
-
+
diff --git a/src/content/docs/rate-limiting/reference.mdx b/src/content/docs/rate-limiting/reference.mdx
index c8a51322..a05abfed 100644
--- a/src/content/docs/rate-limiting/reference.mdx
+++ b/src/content/docs/rate-limiting/reference.mdx
@@ -96,9 +96,9 @@ ajToc:
import { Badge, Aside } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import { frameworks } from "@/lib/prefs";
import Comments from "/src/components/Comments.astro";
@@ -168,7 +168,6 @@ Arcjet rate limiting allows you to define rules which limit the number of
requests a client can make over a period of time.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -206,7 +205,7 @@ type FixedWindowRateLimitOptions = {
#### Fixed window example
-
+
@@ -241,7 +240,7 @@ type SlidingWindowRateLimitOptions = {
#### Sliding window example
-
+
@@ -287,7 +286,7 @@ tokens it wishes to withdraw from the bucket. This is done by passing a
See the [token bucket request example](#token-bucket-request) for how to specify
the number of tokens to request.
-
+
@@ -326,7 +325,7 @@ The custom characteristic is `userId` with the value passed as a prop on the
`protect` function. You can use any string for the characteristic name and any
`string`, `number` or `boolean` for the value.
-
+
@@ -340,7 +339,7 @@ users and a user ID for logged in users, you can use `withRule` to create
augmented clients that use different characteristics. See the
example in the custom characteristics section.
-
+
@@ -354,7 +353,7 @@ Arcjet provides a single `protect` function that is used to execute your
protection rules. This requires a `RequestEvent` property which is the event
context as passed to the request handler.
-
+
@@ -388,7 +387,7 @@ for (const result of decision.results) {
This example will log the full result as well as each rate limit rule:
-
+
@@ -403,7 +402,7 @@ When using a token bucket rule, an additional `requested` prop (positive
integer) should be passed to the `protect` function. This is the number of
tokens the client is requesting to withdraw from the bucket.
-
+
@@ -433,7 +432,7 @@ We provide the [@arcjet/decorate
package](https://github.com/arcjet/arcjet-js/tree/main/decorate) for decorating
your responses with appropriate `RateLimit` headers based on a decision.
-
+
@@ -467,7 +466,7 @@ error result for more information.
If all other rules that were run returned an `ALLOW` result, then the final Arcjet
conclusion will be `ERROR`.
-
+
@@ -488,7 +487,7 @@ Arcjet can also be triggered based using a sample of your traffic.
See the Testing section of the docs for
details.
-
+
diff --git a/src/content/docs/sdk/bun/plus/hono/get-started.mdx b/src/content/docs/sdk/bun/plus/hono/get-started.mdx
new file mode 100644
index 00000000..36eb36b4
--- /dev/null
+++ b/src/content/docs/sdk/bun/plus/hono/get-started.mdx
@@ -0,0 +1,10 @@
+---
+title: "Get started with Bun + Hono"
+description: "Getting started with Arcjet and Bun + Hono."
+prev: false
+next: false
+---
+
+import Guide from "@/content/docs/get-started.mdx";
+
+
diff --git a/src/content/docs/sdk/node/plus/express/get-started.mdx b/src/content/docs/sdk/node/plus/express/get-started.mdx
new file mode 100644
index 00000000..7f7a934a
--- /dev/null
+++ b/src/content/docs/sdk/node/plus/express/get-started.mdx
@@ -0,0 +1,10 @@
+---
+title: "Get started with Node.js + Express"
+description: "Getting started with Arcjet and Node.js + Express."
+prev: false
+next: false
+---
+
+import Guide from "@/content/docs/get-started.mdx";
+
+
diff --git a/src/content/docs/sdk/node/plus/hono/get-started.mdx b/src/content/docs/sdk/node/plus/hono/get-started.mdx
new file mode 100644
index 00000000..d0920bec
--- /dev/null
+++ b/src/content/docs/sdk/node/plus/hono/get-started.mdx
@@ -0,0 +1,10 @@
+---
+title: "Get started with Node.js + Hono"
+description: "Getting started with Arcjet and Node.js + Hono."
+prev: false
+next: false
+---
+
+import Guide from "@/content/docs/get-started.mdx";
+
+
diff --git a/src/content/docs/sdk/python/plus/fastapi/get-started.mdx b/src/content/docs/sdk/python/plus/fastapi/get-started.mdx
new file mode 100644
index 00000000..68473c1e
--- /dev/null
+++ b/src/content/docs/sdk/python/plus/fastapi/get-started.mdx
@@ -0,0 +1,10 @@
+---
+title: "Get started with Python + FastAPI"
+description: "Getting started with Arcjet and Python + FastAPI."
+prev: false
+next: false
+---
+
+import Guide from "@/content/docs/get-started.mdx";
+
+
diff --git a/src/content/docs/sdk/python/plus/flask/get-started.mdx b/src/content/docs/sdk/python/plus/flask/get-started.mdx
new file mode 100644
index 00000000..83fc588e
--- /dev/null
+++ b/src/content/docs/sdk/python/plus/flask/get-started.mdx
@@ -0,0 +1,10 @@
+---
+title: "Get started with Python + Flask"
+description: "Getting started with Arcjet and Python + Flask."
+prev: false
+next: false
+---
+
+import Guide from "@/content/docs/get-started.mdx";
+
+
diff --git a/src/content/docs/sensitive-info/quick-start.mdx b/src/content/docs/sensitive-info/quick-start.mdx
index e889df49..ecd45c7f 100644
--- a/src/content/docs/sensitive-info/quick-start.mdx
+++ b/src/content/docs/sensitive-info/quick-start.mdx
@@ -42,8 +42,8 @@ ajToc:
import { CardGrid } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
import WhatIsArcjet from "/src/components/WhatIsArcjet.astro";
-import SlotByFramework from "@/components/SlotByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import FrameworkName from "@/components/FrameworkName";
import { frameworks } from "@/lib/prefs";
import FAQs from "/src/components/FAQs.astro";
@@ -82,7 +82,6 @@ you do not wish to handle.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -98,7 +97,7 @@ app.
In your project root, run the following command to install the SDK:
-
+
@@ -113,7 +112,7 @@ In your project root, run the following command to install the SDK:
instructions to add a site and get a key. Add it to a `.env.local` file in your
project root.
-
+
@@ -129,7 +128,7 @@ credit/debit card numbers, IP addresses, phone numbers, and/or implement a
custom detection function. See the
reference for details.
-
+
@@ -143,7 +142,7 @@ custom detection function. See the
Start your app and try making a request with an email address in the body of the
request:
-
+
diff --git a/src/content/docs/sensitive-info/reference.mdx b/src/content/docs/sensitive-info/reference.mdx
index a38fc2a8..c245848e 100644
--- a/src/content/docs/sensitive-info/reference.mdx
+++ b/src/content/docs/sensitive-info/reference.mdx
@@ -62,9 +62,9 @@ ajToc:
import { Badge } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import DisplayType from "@/components/DisplayType.astro";
import { frameworks } from "@/lib/prefs";
@@ -100,7 +100,6 @@ Arcjet Sensitive Information Detection protects against clients sending you
sensitive information such as PII that you do not wish to handle.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -152,7 +151,7 @@ execution ordering is automatically optimized for performance. See
[decision](#decision) below for details of examining the execution results.
:::
-
+
@@ -164,7 +163,7 @@ execution ordering is automatically optimized for performance. See
Arcjet provides a single `protect` function that is used to execute your
protection rules.
-
+
@@ -198,7 +197,7 @@ for (const result of decision.results) {
This example will log the full result as well as the sensitive info rule:
-
+
@@ -268,7 +267,7 @@ to the function is controlled by the `contextWindowSize` option, which defaults
to 1. If you need additional context to perform detections then you can increase
this value.
-
+
@@ -293,7 +292,7 @@ error result for more information.
If all other rules that were run returned an `ALLOW` result, then the final Arcjet
conclusion will be `ERROR`.
-
+
diff --git a/src/content/docs/shield/quick-start.mdx b/src/content/docs/shield/quick-start.mdx
index bb4ef608..53beb607 100644
--- a/src/content/docs/shield/quick-start.mdx
+++ b/src/content/docs/shield/quick-start.mdx
@@ -43,8 +43,8 @@ ajToc:
---
import FrameworkName from "@/components/FrameworkName";
-import SlotByFramework from "@/components/SlotByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import { frameworks } from "@/lib/prefs";
import { Link } from "@/components/link";
import { CardGrid } from "@astrojs/starlight/components";
@@ -86,7 +86,6 @@ the [OWASP Top 10](https://owasp.org/www-project-top-ten/).
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -102,7 +101,7 @@ application from common attacks.
In your project root, run the following command to install the SDK:
-
+
@@ -117,7 +116,7 @@ In your project root, run the following command to install the SDK:
instructions to add a site and get a key. Add it to a `.env.local` file in your
project root.
-
+
@@ -128,7 +127,7 @@ project root.
### 3. Protect a route
-
+
@@ -137,7 +136,7 @@ project root.
-
+
@@ -153,7 +152,7 @@ To see Arcjet Shield WAF in action, try making a request with the special header
triggered and will block the request. This simulates the threshold being reached
and is a constant, so you can use it as part of your tests.
-
+
diff --git a/src/content/docs/shield/reference.mdx b/src/content/docs/shield/reference.mdx
index d7af5d7e..8d3282b6 100644
--- a/src/content/docs/shield/reference.mdx
+++ b/src/content/docs/shield/reference.mdx
@@ -61,9 +61,9 @@ ajToc:
import { Badge } from "@astrojs/starlight/components";
import { Link } from "@/components/link";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import DisplayType from "@/components/DisplayType.astro";
import { frameworks } from "@/lib/prefs";
@@ -91,7 +91,6 @@ Arcjet Shield WAF protects your application against common attacks, including
the [OWASP Top 10](https://owasp.org/www-project-top-ten/).
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -115,7 +114,7 @@ execution ordering is automatically optimized for performance. See
[decision](#decision) below for details of examining the execution results.
:::
-
+
@@ -129,7 +128,7 @@ Arcjet provides a single `protect` function that is used to execute your
protection rules. This requires a `RequestEvent` property which is the event
context as passed to the request handler.
-
+
@@ -163,7 +162,7 @@ for (const result of decision.results) {
This example will log the full result as well as the shield rule:
-
+
@@ -186,7 +185,7 @@ error result for more information.
If all other rules that were run returned an `ALLOW` result, then the final Arcjet
conclusion will be `ERROR`.
-
+
diff --git a/src/content/docs/signup-protection/quick-start.mdx b/src/content/docs/signup-protection/quick-start.mdx
index c55e41a9..b6411741 100644
--- a/src/content/docs/signup-protection/quick-start.mdx
+++ b/src/content/docs/signup-protection/quick-start.mdx
@@ -42,9 +42,9 @@ ajToc:
import { CardGrid } from "@astrojs/starlight/components";
import WhatIsArcjet from "/src/components/WhatIsArcjet.astro";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { Link } from "@/components/link";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import FrameworkName from "@/components/FrameworkName";
import { frameworks } from "@/lib/prefs";
import SdkReferenceLinkByFramework from "/src/components/SdkReferenceLinkByFramework.astro";
@@ -77,7 +77,6 @@ Arcjet signup form protection combines rate limiting, bot protection, and email
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -92,7 +91,7 @@ This guide will show you how to protect your signu
In your project root, run the following command to install the SDK:
-
+
@@ -106,7 +105,7 @@ In your project root, run the following command to install the SDK:
[Create a free Arcjet account](https://app.arcjet.com) then follow the
instructions to add a site and get a key. Add it to a `.env.local` file in your project root.
-
+
@@ -126,7 +125,7 @@ primitives. These are configured using our recommended rules.
The example below is a simple email form. You could adapt this as part of a
signup form.
-
+
@@ -135,7 +134,7 @@ signup form.
-
+
diff --git a/src/content/docs/signup-protection/reference.mdx b/src/content/docs/signup-protection/reference.mdx
index 65b1e9e6..557c0d74 100644
--- a/src/content/docs/signup-protection/reference.mdx
+++ b/src/content/docs/signup-protection/reference.mdx
@@ -30,10 +30,10 @@ ajToc:
---
import { Badge } from "@astrojs/starlight/components";
-import SlotByFramework from "@/components/SlotByFramework";
-import TextByFramework from "@/components/TextByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
+import TextByFramework from "@/components/TextByFramework.astro";
import { Link } from "@/components/link";
-import FrameworkLinks from "@/components/FrameworkLinks";
+import FrameworkLinks from "@/components/FrameworkLinks.astro";
import { frameworks } from "@/lib/prefs";
import Comments from "/src/components/Comments.astro";
@@ -65,7 +65,6 @@ Arcjet signup form protection combines rate limiting, bot protection, and email
validation to protect your signup forms from abuse.
!frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
@@ -105,7 +104,7 @@ Our recommended configuration for most signup forms is:
This can be configured as follows:
-
+
@@ -126,7 +125,7 @@ Even in dry run mode each rule will still be evaluated, so you can still [check
the rule results](#checking-rule-results) to see if the email address is valid
or not, or log them to your database.
-
+
@@ -137,7 +136,7 @@ protection rules. This requires a `request` argument which is the request
context as passed to the request handler. When configured with a `protectSignup`
rule it also requires an additional `email` prop.
-
+
@@ -189,7 +188,7 @@ own verification logic. For example, you could decide to manually verify user
signups that come from IP addresses associated with proxies or Tor, and any
users who sign up with a free email address.
-
+
@@ -212,7 +211,7 @@ error result for more information.
If all other rules that were run returned an `ALLOW` result, then the final Arcjet
conclusion will be `ERROR`.
-
+
diff --git a/src/lib/sdk.ts b/src/lib/sdk.ts
index b7250383..ed390b42 100644
--- a/src/lib/sdk.ts
+++ b/src/lib/sdk.ts
@@ -20,6 +20,64 @@ export type ArcjetSdkKey =
| "remix"
| "sveltekit";
+/**
+ * A sub-variant of an SDK that uses the same Arcjet SDK package but
+ * pairs it with a different framework (e.g. Bun + Hono, Node.js + Express).
+ */
+export type ArcjetSdkVariant = {
+ /** URL-safe key used in `/sdk/:sdk/plus/:variant/` paths */
+ readonly key: string;
+ /** Human readable label */
+ readonly label: string;
+ /** Maps to a legacy FrameworkKey for slot resolution */
+ readonly legacyFrameworkKey: FrameworkKey;
+};
+
+/**
+ * Sub-variants for SDKs that support multiple framework pairings.
+ */
+const SDK_VARIANTS: Partial> =
+ {
+ bun: [{ key: "hono", label: "Hono", legacyFrameworkKey: "bun-hono" }],
+ node: [
+ {
+ key: "express",
+ label: "Express",
+ legacyFrameworkKey: "node-js-express",
+ },
+ { key: "hono", label: "Hono", legacyFrameworkKey: "node-js-hono" },
+ ],
+ python: [
+ {
+ key: "fastapi",
+ label: "FastAPI",
+ legacyFrameworkKey: "python-fastapi",
+ },
+ { key: "flask", label: "Flask", legacyFrameworkKey: "python-flask" },
+ ],
+ } as const;
+
+/**
+ * Returns the sub-variants for a given SDK, or an empty array if none.
+ */
+export function sdkVariants(sdkKey: ArcjetSdkKey): readonly ArcjetSdkVariant[] {
+ return SDK_VARIANTS[sdkKey] ?? [];
+}
+
+/**
+ * Returns all SDKs that have sub-variants.
+ */
+export function sdksWithVariants(): [
+ ArcjetSdkKey,
+ readonly ArcjetSdkVariant[],
+][] {
+ // Object.entries widens keys to `string`; narrow back to ArcjetSdkKey.
+ return Object.entries(SDK_VARIANTS) as [
+ ArcjetSdkKey,
+ readonly ArcjetSdkVariant[],
+ ][];
+}
+
/**
* Documentation configuration object for an Arcjet SDK
*/
@@ -132,6 +190,47 @@ export function isSdkKey(value: string): value is ArcjetSdkKey {
}
const SDK_PATH_REGEX = /^\/sdk\/([a-z-]+)/;
+const SDK_PLUS_PATH_REGEX = /^\/sdk\/([a-z-]+)\/plus\/([a-z-]+)/;
+
+/**
+ * Extracts an Arcjet SDK variant key from a `/sdk/:sdk/plus/:variant/` pathname.
+ * Returns `undefined` if the path is not a plus-variant route.
+ */
+export function sdkVariantFromPathname(
+ pathname: string,
+): ArcjetSdkVariant | undefined {
+ if (typeof pathname !== "string") return undefined;
+
+ const match = pathname.match(SDK_PLUS_PATH_REGEX);
+ if (!match) return undefined;
+
+ const sdkKey = match[1];
+ const variantKey = match[2];
+
+ if (!isSdkKey(sdkKey)) return undefined;
+
+ const variants = SDK_VARIANTS[sdkKey];
+ return variants?.find((v) => v.key === variantKey);
+}
+
+/**
+ * Resolves the legacy FrameworkKey for the current SDK-scoped pathname.
+ *
+ * For plus-variant paths like `/sdk/bun/plus/hono/...`, returns the variant's
+ * legacy key (e.g. `"bun-hono"`). For plain SDK paths like `/sdk/next/...`,
+ * returns the SDK's legacy key (e.g. `"next-js"`).
+ */
+export function legacyKeyFromPathname(
+ pathname: string,
+): FrameworkKey | undefined {
+ const variant = sdkVariantFromPathname(pathname);
+ if (variant) return variant.legacyFrameworkKey;
+
+ const sdkKey = sdkFromPathname(pathname);
+ if (!sdkKey) return undefined;
+
+ return ARCJET_SDKS[sdkKey].legacyFrameworkKey ?? undefined;
+}
/**
* Extracts an Arcjet SDK key from a given pathname, if possible.
@@ -160,8 +259,15 @@ export function sdkFromPathname(pathname: string): ArcjetSdkKey | undefined {
/**
* Returns a pathname scoped to the given SDK.
+ *
+ * If the current path is a plus-variant route (e.g. `/sdk/bun/plus/hono/foo`),
+ * the variant segment is stripped when switching to a different SDK because
+ * variants are SDK-specific.
*/
-export function pathnameForSdk(pathname: string, sdk: ArcjetSdkKey): string {
+export function pathnameForSdk(
+ pathname: string,
+ targetSdk: ArcjetSdkKey,
+): string {
const previousSdk = sdkFromPathname(pathname);
if (!previousSdk) {
@@ -170,7 +276,48 @@ export function pathnameForSdk(pathname: string, sdk: ArcjetSdkKey): string {
);
}
- return pathname.replace(`/sdk/${previousSdk}`, `/sdk/${sdk}`);
+ // Strip any /plus/:variant/ segment since variants are SDK-specific
+ const plusVariant = sdkVariantFromPathname(pathname);
+ let cleanPathname = pathname;
+ if (plusVariant) {
+ cleanPathname = pathname.replace(`/plus/${plusVariant.key}`, "");
+ }
+
+ return cleanPathname.replace(`/sdk/${previousSdk}`, `/sdk/${targetSdk}`);
+}
+
+/**
+ * Returns a pathname scoped to a specific SDK variant.
+ *
+ * @example
+ * pathnameForSdkVariant("/sdk/bun/get-started", "bun", "hono")
+ * // => "/sdk/bun/plus/hono/get-started"
+ */
+export function pathnameForSdkVariant(
+ pathname: string,
+ sdkKey: ArcjetSdkKey,
+ variantKey: string,
+): string {
+ const previousSdk = sdkFromPathname(pathname);
+
+ if (!previousSdk) {
+ throw new Error(
+ `@/lib/sdk:pathnameForSdkVariant only supports SDK scoped pathnames.`,
+ );
+ }
+
+ // Strip any existing /plus/:variant/ segment
+ const existingVariant = sdkVariantFromPathname(pathname);
+ let cleanPathname = pathname;
+ if (existingVariant) {
+ cleanPathname = pathname.replace(`/plus/${existingVariant.key}`, "");
+ }
+
+ // Replace SDK and inject variant
+ return cleanPathname.replace(
+ `/sdk/${previousSdk}`,
+ `/sdk/${sdkKey}/plus/${variantKey}`,
+ );
}
/**
diff --git a/src/snippets/bot-protection/reference/nextjs/PerRouteVsMiddleware.mdx b/src/snippets/bot-protection/reference/nextjs/PerRouteVsMiddleware.mdx
index a3a8a20e..cff61d56 100644
--- a/src/snippets/bot-protection/reference/nextjs/PerRouteVsMiddleware.mdx
+++ b/src/snippets/bot-protection/reference/nextjs/PerRouteVsMiddleware.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import MiddlewareNextJs from "./Middleware.mdx";
import PerRouteNextJs from "./PerRoute.mdx";
@@ -21,12 +21,12 @@ Bot protection rules can be configured in two ways:
This configures bot protection on a single route.
-
+
### Middleware
-
+
diff --git a/src/snippets/bot-protection/reference/remix/LoaderVsAction.mdx b/src/snippets/bot-protection/reference/remix/LoaderVsAction.mdx
index 157c8b34..9b3cf8ba 100644
--- a/src/snippets/bot-protection/reference/remix/LoaderVsAction.mdx
+++ b/src/snippets/bot-protection/reference/remix/LoaderVsAction.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { Code } from "@astrojs/starlight/components";
import ActionTS from "./Action.ts?raw";
import ActionJS from "./Action.js?raw";
diff --git a/src/snippets/bot-protection/reference/sveltekit/PerRouteVsHooks.mdx b/src/snippets/bot-protection/reference/sveltekit/PerRouteVsHooks.mdx
index d2acfd9d..9079568d 100644
--- a/src/snippets/bot-protection/reference/sveltekit/PerRouteVsHooks.mdx
+++ b/src/snippets/bot-protection/reference/sveltekit/PerRouteVsHooks.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import Hooks from "./Hooks.mdx";
import PerRoute from "./PerRoute.mdx";
import { Code } from "@astrojs/starlight/components";
diff --git a/src/snippets/rate-limiting/reference/nextjs/PerRouteVsMiddleware.mdx b/src/snippets/rate-limiting/reference/nextjs/PerRouteVsMiddleware.mdx
index 6959783d..9dfd348a 100644
--- a/src/snippets/rate-limiting/reference/nextjs/PerRouteVsMiddleware.mdx
+++ b/src/snippets/rate-limiting/reference/nextjs/PerRouteVsMiddleware.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import MiddlewareMatchingPaths from "./MiddlewareMatchingPaths.mdx";
import MiddlewareAllRoutes from "./MiddlewareAllRoutes.mdx";
import MiddlewareMatcher from "./MiddlewareMatcher.mdx";
@@ -35,13 +35,13 @@ the middleware for, or use `request.nextUrl.pathname.startsWith`.
You can use conditionals in your Next.js middleware to match multiple paths.
-
+
#### Middleware
-
+
@@ -56,6 +56,6 @@ For example, if you already have a rate limit defined in the API route at
`/api/hello`, you can exclude it from the middleware by specifying a matcher in
`/proxy.ts`:
-
+
diff --git a/src/snippets/rate-limiting/reference/remix/LoaderVsAction.mdx b/src/snippets/rate-limiting/reference/remix/LoaderVsAction.mdx
index d4258ec9..bbefba3a 100644
--- a/src/snippets/rate-limiting/reference/remix/LoaderVsAction.mdx
+++ b/src/snippets/rate-limiting/reference/remix/LoaderVsAction.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { Code } from "@astrojs/starlight/components";
import ActionTS from "./Action.ts?raw";
import ActionJS from "./Action.js?raw";
diff --git a/src/snippets/rate-limiting/reference/sveltekit/PerRouteVsHooks.mdx b/src/snippets/rate-limiting/reference/sveltekit/PerRouteVsHooks.mdx
index 840004d6..8320000e 100644
--- a/src/snippets/rate-limiting/reference/sveltekit/PerRouteVsHooks.mdx
+++ b/src/snippets/rate-limiting/reference/sveltekit/PerRouteVsHooks.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import HookMatchingPaths from "./HookMatchingPaths.mdx";
import HookAllRoutes from "./HookAllRoutes.mdx";
import HookMatcher from "./HookMatcher.mdx";
diff --git a/src/snippets/sensitive-info/reference/nextjs/PerRouteVsMiddleware.mdx b/src/snippets/sensitive-info/reference/nextjs/PerRouteVsMiddleware.mdx
index a3af8e29..69b3f889 100644
--- a/src/snippets/sensitive-info/reference/nextjs/PerRouteVsMiddleware.mdx
+++ b/src/snippets/sensitive-info/reference/nextjs/PerRouteVsMiddleware.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import MiddlewareNextJs from "./Middleware.mdx";
import PerRouteNextJs from "./PerRoute.mdx";
@@ -19,12 +19,12 @@ Bot protection rules can be configured in two ways:
This configures bot protection on a single route.
-
+
### Middleware
-
+
diff --git a/src/snippets/sensitive-info/reference/sveltekit/PerRouteVsHooks.mdx b/src/snippets/sensitive-info/reference/sveltekit/PerRouteVsHooks.mdx
index 94fac243..fc4315aa 100644
--- a/src/snippets/sensitive-info/reference/sveltekit/PerRouteVsHooks.mdx
+++ b/src/snippets/sensitive-info/reference/sveltekit/PerRouteVsHooks.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import Hooks from "./Hooks.mdx";
import PerRoute from "./PerRoute.mdx";
import { Code } from "@astrojs/starlight/components";
diff --git a/src/snippets/shield/reference/nextjs/PerRouteVsMiddleware.mdx b/src/snippets/shield/reference/nextjs/PerRouteVsMiddleware.mdx
index e58d5308..bf11a58d 100644
--- a/src/snippets/shield/reference/nextjs/PerRouteVsMiddleware.mdx
+++ b/src/snippets/shield/reference/nextjs/PerRouteVsMiddleware.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import MiddlewareNextJs from "./Middleware.mdx";
import PerRouteNextJs from "./PerRoute.mdx";
diff --git a/src/snippets/shield/reference/remix/LoaderVsAction.mdx b/src/snippets/shield/reference/remix/LoaderVsAction.mdx
index f236a341..e5761888 100644
--- a/src/snippets/shield/reference/remix/LoaderVsAction.mdx
+++ b/src/snippets/shield/reference/remix/LoaderVsAction.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import { Code } from "@astrojs/starlight/components";
import ActionTS from "./Action.ts?raw";
import ActionJS from "./Action.js?raw";
diff --git a/src/snippets/shield/reference/sveltekit/PerRouteVsHooks.mdx b/src/snippets/shield/reference/sveltekit/PerRouteVsHooks.mdx
index d2a92ace..f13f866c 100644
--- a/src/snippets/shield/reference/sveltekit/PerRouteVsHooks.mdx
+++ b/src/snippets/shield/reference/sveltekit/PerRouteVsHooks.mdx
@@ -1,5 +1,5 @@
import SelectableContent from "@/components/SelectableContent";
-import SlotByFramework from "@/components/SlotByFramework";
+import SlotByFramework from "@/components/SlotByFramework.astro";
import Hooks from "./Hooks.mdx";
import PerRoute from "./PerRoute.mdx";
import { Code } from "@astrojs/starlight/components";
diff --git a/tests/sdk-routes.test.ts b/tests/sdk-routes.test.ts
new file mode 100644
index 00000000..35bf16e1
--- /dev/null
+++ b/tests/sdk-routes.test.ts
@@ -0,0 +1,257 @@
+import { expect, test, type Page } from "@playwright/test";
+import { sdks, sdkVariants } from "@/lib/sdk";
+
+/**
+ * Validates that per-SDK route pages (`/sdk/:sdk/...`) render the same main
+ * content as the legacy `?f=` pages.
+ *
+ * The right sidebar (TOC / SDK switcher) will differ, so we only screenshot
+ * the `` element.
+ */
+
+// These pages are heavy — give each test plenty of time.
+test.setTimeout(60_000);
+
+interface SdkRouteEntry {
+ /** Legacy `?f=` framework key */
+ legacyKey: string;
+ /** New SDK-scoped route prefix, e.g. "/sdk/next" or "/sdk/bun/plus/hono" */
+ sdkPrefix: string;
+}
+
+const SDK_ROUTE_ENTRIES: SdkRouteEntry[] = [];
+for (const sdk of sdks()) {
+ if (sdk.legacyFrameworkKey) {
+ SDK_ROUTE_ENTRIES.push({
+ legacyKey: sdk.legacyFrameworkKey,
+ sdkPrefix: `/sdk/${sdk.key}`,
+ });
+ }
+ for (const variant of sdkVariants(sdk.key)) {
+ SDK_ROUTE_ENTRIES.push({
+ legacyKey: variant.legacyFrameworkKey,
+ sdkPrefix: `/sdk/${sdk.key}/plus/${variant.key}`,
+ });
+ }
+}
+
+interface PageSpec {
+ /** Path without leading `/sdk/…` prefix, e.g. "/get-started" */
+ path: string;
+ /**
+ * Subset of legacy framework keys this page supports.
+ * When omitted, all entries from SDK_ROUTE_ENTRIES are tested.
+ */
+ frameworks?: readonly string[];
+}
+
+const PAGES: readonly PageSpec[] = [
+ {
+ path: "/get-started",
+ frameworks: [
+ "astro",
+ "bun",
+ "bun-hono",
+ "deno",
+ "fastify",
+ "nest-js",
+ "next-js",
+ "node-js",
+ "node-js-express",
+ "node-js-hono",
+ "nuxt",
+ "python-fastapi",
+ "python-flask",
+ "react-router",
+ "remix",
+ "sveltekit",
+ ],
+ },
+ {
+ path: "/bot-protection/quick-start",
+ frameworks: [
+ "astro",
+ "bun",
+ "deno",
+ "nest-js",
+ "next-js",
+ "node-js",
+ "nuxt",
+ "remix",
+ "sveltekit",
+ ],
+ },
+ {
+ path: "/bot-protection/reference",
+ frameworks: [
+ "bun",
+ "deno",
+ "nest-js",
+ "next-js",
+ "node-js",
+ "remix",
+ "sveltekit",
+ ],
+ },
+ {
+ path: "/email-validation/quick-start",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/email-validation/reference",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/filters/quick-start",
+ frameworks: [
+ "astro",
+ "bun",
+ "deno",
+ "fastify",
+ "nest-js",
+ "next-js",
+ "node-js",
+ "react-router",
+ "remix",
+ "sveltekit",
+ ],
+ },
+ {
+ path: "/filters/reference",
+ frameworks: [
+ "bun",
+ "deno",
+ "nest-js",
+ "next-js",
+ "node-js",
+ "remix",
+ "sveltekit",
+ ],
+ },
+ {
+ path: "/nosecone/quick-start",
+ frameworks: ["bun", "deno", "next-js", "node-js", "sveltekit"],
+ },
+ {
+ path: "/rate-limiting/quick-start",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/rate-limiting/reference",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/sensitive-info/quick-start",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/sensitive-info/reference",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/shield/quick-start",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/shield/reference",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/signup-protection/quick-start",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+ {
+ path: "/signup-protection/reference",
+ frameworks: ["bun", "nest-js", "next-js", "node-js", "remix", "sveltekit"],
+ },
+];
+
+/** Remove elements that cause non-deterministic rendering. */
+async function sanitizePage(page: Page) {
+ // Hide rather than remove — works even with JS disabled.
+ const selectors = ["astro-dev-toolbar", "lite-youtube", "div.giscus"];
+
+ for (const sel of selectors) {
+ for (const el of await page.locator(sel).all()) {
+ await el.evaluate((node) => (node.style.display = "none"));
+ }
+ }
+}
+
+/**
+ * Screenshot the `` element after sanitisation.
+ *
+ * Returns the screenshot buffer so the caller can compare two of them.
+ */
+async function screenshotMain(page: Page): Promise {
+ await sanitizePage(page);
+
+ const main = page.locator("main");
+ await main.waitFor({ state: "visible" });
+
+ return (await main.screenshot()) as Buffer;
+}
+
+/**
+ * Wait for Astro islands to hydrate.
+ *
+ * On legacy pages `SlotByFrameworkReact` renders a `.Skeleton` placeholder
+ * until hydration. On *both* legacy and SDK pages, `SelectableContent`
+ * islands need to hydrate before their tabbed content becomes visible.
+ *
+ * We wait for all `astro-island` elements inside `` to drop their
+ * `ssr` attribute, which Astro removes once an island has hydrated.
+ */
+async function waitForHydration(page: Page) {
+ await page
+ .locator("main astro-island[ssr]")
+ .first()
+ .waitFor({ state: "detached", timeout: 15_000 })
+ .catch(() => {
+ // No un-hydrated islands — that's fine.
+ });
+
+ // Give a short extra beat for any re-renders to settle.
+ await page.waitForTimeout(500);
+}
+
+for (const spec of PAGES) {
+ const entries = spec.frameworks
+ ? SDK_ROUTE_ENTRIES.filter((e) => spec.frameworks!.includes(e.legacyKey))
+ : SDK_ROUTE_ENTRIES;
+
+ test.describe(`Main-content parity: ${spec.path}`, () => {
+ for (const { legacyKey, sdkPrefix } of entries) {
+ const legacyUrl = `${spec.path}?f=${legacyKey}`;
+ const sdkUrl = `${sdkPrefix}${spec.path}`;
+ const safeName = `${spec.path.slice(1).replaceAll("/", "-")}-${legacyKey}`;
+
+ test(`${legacyUrl} vs ${sdkUrl}`, async ({ page }) => {
+ const legacyRes = await page.goto(legacyUrl, {
+ waitUntil: "networkidle",
+ });
+ expect(legacyRes?.ok(), `Legacy page ${legacyUrl} failed`).toBeTruthy();
+ await waitForHydration(page);
+
+ const legacyShot = await screenshotMain(page);
+
+ const sdkRes = await page.goto(sdkUrl, {
+ waitUntil: "networkidle",
+ });
+ expect(sdkRes?.ok(), `SDK page ${sdkUrl} failed`).toBeTruthy();
+ await waitForHydration(page);
+
+ const sdkShot = await screenshotMain(page);
+
+ expect(sdkShot).toMatchSnapshot(`sdk-parity-${safeName}.png`, {
+ maxDiffPixels: 200,
+ threshold: 0.15,
+ });
+ expect(legacyShot).toMatchSnapshot(`sdk-parity-${safeName}.png`, {
+ maxDiffPixels: 200,
+ threshold: 0.15,
+ });
+ });
+ }
+ });
+}
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-astro-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-astro-chromium-linux.png
new file mode 100644
index 00000000..44a6a2cf
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-astro-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..3e6bd517
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-deno-chromium-linux.png
new file mode 100644
index 00000000..0b6e3989
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..48538aed
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..5fe2f830
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..3caaeed9
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nuxt-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nuxt-chromium-linux.png
new file mode 100644
index 00000000..581d7254
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-nuxt-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..57253d15
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..3668fc46
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..6a45b893
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-deno-chromium-linux.png
new file mode 100644
index 00000000..8733ca72
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..c6e8798f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..211b8ea5
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..e524b16a
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..d28c7017
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..0d449403
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-bot-protection-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..d378ba2a
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..0448225d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..e76b6eac
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..11231c78
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..c5dfe545
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..180cd163
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..6cee0b7d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..b32129f1
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..7683f43f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..527e7f1d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..9d54804b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..68d8fa45
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-email-validation-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-astro-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-astro-chromium-linux.png
new file mode 100644
index 00000000..8c911a8b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-astro-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..103f2d9b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-deno-chromium-linux.png
new file mode 100644
index 00000000..23aa3bc2
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-fastify-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-fastify-chromium-linux.png
new file mode 100644
index 00000000..1e52c904
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-fastify-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..f8c68f19
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..2066d811
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..e2e85a58
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-react-router-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-react-router-chromium-linux.png
new file mode 100644
index 00000000..06998812
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-react-router-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..d79078f7
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..2b03468e
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..fe114e8c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-deno-chromium-linux.png
new file mode 100644
index 00000000..442e63b8
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..536caf43
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..312abc1f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..19e06ec7
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..c40e3c3e
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..e3e83d2d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-filters-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-astro-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-astro-chromium-linux.png
new file mode 100644
index 00000000..0e3a5680
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-astro-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-chromium-linux.png
new file mode 100644
index 00000000..2d9e635c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-hono-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-hono-chromium-linux.png
new file mode 100644
index 00000000..bb18e2e6
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-bun-hono-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-deno-chromium-linux.png
new file mode 100644
index 00000000..2734c35c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-fastify-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-fastify-chromium-linux.png
new file mode 100644
index 00000000..2692b5b4
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-fastify-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nest-js-chromium-linux.png
new file mode 100644
index 00000000..72c1e18e
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-next-js-chromium-linux.png
new file mode 100644
index 00000000..c6f644a3
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-chromium-linux.png
new file mode 100644
index 00000000..ade053e7
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-express-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-express-chromium-linux.png
new file mode 100644
index 00000000..a6853aa2
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-express-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-hono-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-hono-chromium-linux.png
new file mode 100644
index 00000000..60c9e756
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-node-js-hono-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nuxt-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nuxt-chromium-linux.png
new file mode 100644
index 00000000..15944fae
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-nuxt-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-fastapi-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-fastapi-chromium-linux.png
new file mode 100644
index 00000000..8fe08e38
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-fastapi-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-flask-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-flask-chromium-linux.png
new file mode 100644
index 00000000..66082a90
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-python-flask-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-react-router-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-react-router-chromium-linux.png
new file mode 100644
index 00000000..f7624101
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-react-router-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-remix-chromium-linux.png
new file mode 100644
index 00000000..a1dfef98
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..0d57da00
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-get-started-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..1cfd064d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-deno-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-deno-chromium-linux.png
new file mode 100644
index 00000000..2169b2d7
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-deno-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..0b5c2aa3
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..0540a49f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..e5826a10
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-nosecone-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..80072bc9
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..d273ec3d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..4b0ec5b1
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..6306eeff
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..3bae37f6
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..d03d6383
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..cb2e0f8c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..f4958d4b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..1c793eb5
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..b380d5c3
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..8fc68ec4
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..38c337a6
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-rate-limiting-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..b82bd865
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..7303383d
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..13988a0c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..9e87cfa4
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..70ffb031
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..6e3f1574
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..3660fd28
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..c5c82dbd
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..6d169e75
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..7d795059
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..1f3a7fc0
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..9b298fd4
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-sensitive-info-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..bb61c878
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..c55d7c25
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..b4cfccaf
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..fe303d7e
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..af699749
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..892e1bf5
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..8c8abb2a
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..a141db78
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..618cdb6e
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..3cad954b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..7ae8c73f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..a3b7f815
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-shield-reference-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-bun-chromium-linux.png
new file mode 100644
index 00000000..cccebf4b
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-nest-js-chromium-linux.png
new file mode 100644
index 00000000..969836f2
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-next-js-chromium-linux.png
new file mode 100644
index 00000000..fba63e83
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-node-js-chromium-linux.png
new file mode 100644
index 00000000..eba2092f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-remix-chromium-linux.png
new file mode 100644
index 00000000..ed3934ba
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..f3c507a0
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-quick-start-sveltekit-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-bun-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-bun-chromium-linux.png
new file mode 100644
index 00000000..f37722f1
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-bun-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-nest-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-nest-js-chromium-linux.png
new file mode 100644
index 00000000..8262a56f
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-nest-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-next-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-next-js-chromium-linux.png
new file mode 100644
index 00000000..5d1c7291
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-next-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-node-js-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-node-js-chromium-linux.png
new file mode 100644
index 00000000..02efd54c
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-node-js-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-remix-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-remix-chromium-linux.png
new file mode 100644
index 00000000..259871d6
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-remix-chromium-linux.png differ
diff --git a/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-sveltekit-chromium-linux.png b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-sveltekit-chromium-linux.png
new file mode 100644
index 00000000..397ae6bd
Binary files /dev/null and b/tests/sdk-routes.test.ts-snapshots/sdk-parity-signup-protection-reference-sveltekit-chromium-linux.png differ