Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions src/components/FrameworkLinks.astro
Original file line number Diff line number Diff line change
@@ -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 : (
<FrameworkLinksReact client:load {...Astro.props}>
<slot />
</FrameworkLinksReact>
)
}
4 changes: 2 additions & 2 deletions src/components/SdkReferenceLinkByFramework.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
import SlotByFramework from "@/components/SlotByFramework";
import SlotByFramework from "@/components/SlotByFramework.astro";
import { Link } from "@/components/link";
---

<SlotByFramework client:load>
<SlotByFramework>
<Link.Card
slot="bun"
title="SDK reference"
Expand Down
129 changes: 129 additions & 0 deletions src/components/SlotByFramework.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
/**
* SlotByFramework — Astro wrapper
*
* On SDK-scoped routes (`/sdk/:sdk/...`), renders only the matching slot at
* build time with zero client JavaScript.
*
* On legacy (unscoped) routes, delegates to the React `SlotByFrameworkReact`
* component with `client:load` so the existing client-side framework switching
* via nanostores continues to work.
*/
import SlotByFrameworkReact from "@/components/SlotByFrameworkReact";
import { sdkFromPathname, legacyKeyFromPathname } from "@/lib/sdk";

import "@/components/SlotByFramework.scss";

type Props = {
/** When true, renders the slot content inline without a wrapper div. */
inline?: boolean;
};

const { inline } = Astro.props;
const sdkKey = sdkFromPathname(Astro.url.pathname);

// Resolve the legacy framework key for the current SDK (or variant) so we can
// look up the correct named slot (slots use legacy keys like "next-js", etc.).
const legacyKey = sdkKey ? legacyKeyFromPathname(Astro.url.pathname) : undefined;

const hasMatchingSlot = legacyKey ? Astro.slots.has(legacyKey) : false;
---

{
sdkKey && hasMatchingSlot ? (
inline ? (
<Fragment set:html={await Astro.slots.render(legacyKey!)} />
) : (
<div class="SlotByFramework">
<Fragment set:html={await Astro.slots.render(legacyKey!)} />
</div>
)
) : (
<SlotByFrameworkReact client:load inline={inline}>
{Astro.slots.has("default") && (
<Fragment set:html={await Astro.slots.render("default")} />
)}
{Astro.slots.has("astro") && (
<Fragment slot="astro" set:html={await Astro.slots.render("astro")} />
)}
{Astro.slots.has("bun") && (
<Fragment slot="bun" set:html={await Astro.slots.render("bun")} />
)}
{Astro.slots.has("bun-hono") && (
<Fragment
slot="bun-hono"
set:html={await Astro.slots.render("bun-hono")}
/>
)}
{Astro.slots.has("deno") && (
<Fragment slot="deno" set:html={await Astro.slots.render("deno")} />
)}
{Astro.slots.has("fastify") && (
<Fragment
slot="fastify"
set:html={await Astro.slots.render("fastify")}
/>
)}
{Astro.slots.has("nest-js") && (
<Fragment
slot="nest-js"
set:html={await Astro.slots.render("nest-js")}
/>
)}
{Astro.slots.has("next-js") && (
<Fragment
slot="next-js"
set:html={await Astro.slots.render("next-js")}
/>
)}
{Astro.slots.has("node-js") && (
<Fragment
slot="node-js"
set:html={await Astro.slots.render("node-js")}
/>
)}
{Astro.slots.has("node-js-express") && (
<Fragment
slot="node-js-express"
set:html={await Astro.slots.render("node-js-express")}
/>
)}
{Astro.slots.has("node-js-hono") && (
<Fragment
slot="node-js-hono"
set:html={await Astro.slots.render("node-js-hono")}
/>
)}
{Astro.slots.has("nuxt") && (
<Fragment slot="nuxt" set:html={await Astro.slots.render("nuxt")} />
)}
{Astro.slots.has("python-fastapi") && (
<Fragment
slot="python-fastapi"
set:html={await Astro.slots.render("python-fastapi")}
/>
)}
{Astro.slots.has("python-flask") && (
<Fragment
slot="python-flask"
set:html={await Astro.slots.render("python-flask")}
/>
)}
{Astro.slots.has("react-router") && (
<Fragment
slot="react-router"
set:html={await Astro.slots.render("react-router")}
/>
)}
{Astro.slots.has("remix") && (
<Fragment slot="remix" set:html={await Astro.slots.render("remix")} />
)}
{Astro.slots.has("sveltekit") && (
<Fragment
slot="sveltekit"
set:html={await Astro.slots.render("sveltekit")}
/>
)}
</SlotByFrameworkReact>
)
}
49 changes: 49 additions & 0 deletions src/components/TextByFramework.astro
Original file line number Diff line number Diff line change
@@ -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 ? (
<Fragment set:html={resolvedText} />
) : (
<TextByFrameworkReact client:load {...Astro.props as any}>
<slot />
</TextByFrameworkReact>
)
}
4 changes: 2 additions & 2 deletions src/components/TitleByFramework.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
13 changes: 6 additions & 7 deletions src/content/docs/bot-protection/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -109,7 +109,6 @@ Arcjet bot detection allows you to manage traffic by automated clients and bots.
<WhatIsArcjet />

<FrameworkLinks
client:load
exclude={frameworks
.filter((d) => !frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
Expand All @@ -125,7 +124,7 @@ application from bots and automated clients.

In your project root, run the following command to install the SDK:

<SlotByFramework client:load>
<SlotByFramework>
<AstroStep1 slot="astro" />
<BunStep1 slot="bun" />
<DenoStep1 slot="deno" />
Expand All @@ -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.

<SlotByFramework client:load>
<SlotByFramework>
<AstroStep2 slot="astro" />
<BunStep2 slot="bun" />
<DenoStep2 slot="deno" />
Expand All @@ -156,7 +155,7 @@ instructions to add a site and get a key.

### 3. Add bot protection

<SlotByFramework client:load>
<SlotByFramework>
<AstroStep3 slot="astro" />
<BunStep3 slot="bun" />
<DenoStep3 slot="deno" />
Expand All @@ -168,7 +167,7 @@ instructions to add a site and get a key.
<SvelteKitStep3 slot="sveltekit" />
</SlotByFramework>

<SlotByFramework client:load>
<SlotByFramework>
<AstroStep4 slot="astro" />
<BunStep4 slot="bun" />
<DenoStep4 slot="deno" />
Expand Down
25 changes: 12 additions & 13 deletions src/content/docs/bot-protection/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.

<FrameworkLinks
client:load
exclude={frameworks
.filter((d) => !frontmatter.frameworks.includes(d.key))
.map((d) => d.key)}
Expand Down Expand Up @@ -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
<Link.Page href="/bot-protection/identifying-bots#bot-categories">bot categories</Link.Page>.

<SlotByFramework client:load>
<SlotByFramework>
<BunAllowingBots slot="bun" />
<DenoAllowingBots slot="deno" />
<NestJsAllowingBots slot="nest-js" />
Expand All @@ -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
<Link.Page href="/bot-protection/identifying-bots#bot-categories">bot categories</Link.Page>.

<SlotByFramework client:load>
<SlotByFramework>
<BunDenyingBots slot="bun" />
<DenoDenyingBots slot="deno" />
<NestJsDenyingBots slot="nest-js" />
Expand All @@ -238,7 +237,7 @@ bots](https://arcjet.com/bot-list) and/or
<SvelteKitDenyingBots slot="sveltekit" />
</SlotByFramework>

<SlotByFramework client:load>
<SlotByFramework>
<NestJsDecoratorRoutes slot="nest-js" />
<NextJsPerRouteVsMiddleware slot="next-js" />
<NextJsPagesServerActions slot="next-js" />
Expand All @@ -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.

<SlotByFramework client:load>
<SlotByFramework>
<NestJsGuardDecision slot="nest-js" />
</SlotByFramework>

Expand Down Expand Up @@ -288,7 +287,7 @@ for (const result of decision.results) {
}
```

<SlotByFramework client:load>
<SlotByFramework>
<BunDecisionLog slot="bun" />
<DenoDecisionLog slot="deno" />
<NestJsDecisionLog slot="nest-js" />
Expand All @@ -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.

<SlotByFramework client:load>
<SlotByFramework>
<BunIdentifiedBots slot="bun" />
<DenoIdentifiedBots slot="deno" />
<NestJsIdentifiedBots slot="nest-js" />
Expand Down Expand Up @@ -339,7 +338,7 @@ header.
See <Link.Page href="/bot-protection/reference#user-agent-header">an example of how to do this</Link.Page>.
:::

<SlotByFramework client:load>
<SlotByFramework>
<BunErrors slot="bun" />
<DenoErrors slot="deno" />
<NestJsErrors slot="nest-js" />
Expand All @@ -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.

<SlotByFramework client:load>
<SlotByFramework>
<BunFiltering slot="bun" />
<DenoFiltering slot="deno" />
<NestJsFiltering slot="nest-js" />
Expand Down Expand Up @@ -497,7 +496,7 @@ Arcjet can also be triggered based using a sample of your traffic.

See the <Link.Page href="/testing">Testing</Link.Page> section of the docs for details.

<SlotByFramework client:load>
<SlotByFramework>
<NextJsExamples slot="next-js" />
</SlotByFramework>

Expand Down
Loading
Loading