diff --git a/astro.config.mts b/astro.config.mts index d79efda53..f849056f9 100644 --- a/astro.config.mts +++ b/astro.config.mts @@ -13,6 +13,7 @@ import { isShallowRepository, sitemapLastmodSerializer, } from "./src/lib/content-dates"; +import { shouldExcludeFromSitemap, variantOnlySdkAstroRedirects } from "./src/lib/sdk"; import { main as sidebar } from "./src/lib/sidebars"; /* @@ -173,7 +174,10 @@ export default defineConfig({ ], plugins: [ starlightLinksValidator({ - exclude: ["**/*f=*"], // exclude urls with `f` param from validation + exclude: [ + "**/*f=*", // exclude urls with `f` param from validation + "**/sdk/**", // SDK-scoped routes are generated by the content loader + ], errorOnLocalLinks: false, // we use localhost in the examples // TODO(#494) enable once we've sorted out the issue it's having with the // troubleshooting section. Specifically @@ -221,6 +225,7 @@ export default defineConfig({ * needs adding here too. */ sitemap({ + filter: (page) => !shouldExcludeFromSitemap(new URL(page).pathname), serialize: sitemapLastmodSerializer(), }), react(), @@ -228,67 +233,70 @@ export default defineConfig({ ], // External redirects go in /vercel.json redirects: { - "/get-started/bun": "/get-started?f=bun", - "/get-started/nextjs": "/get-started?f=next-js", - "/get-started/nodejs": "/get-started?f=node-js", - "/get-started/fastify": "/get-started?f=fastify", - "/get-started/sveltekit": "/get-started?f=sveltekit", - "/shield/quick-start/bun": "/shield/quick-start?f=bun", - "/shield/quick-start/nextjs": "/shield/quick-start?f=next-js", - "/shield/quick-start/nodejs": "/shield/quick-start?f=node-js", - "/shield/quick-start/sveltekit": "/shield/quick-start?f=sveltekit", - "/shield/reference/bun": "/shield/reference?f=bun", - "/shield/reference/nextjs": "/shield/reference?f=next-js", - "/shield/reference/nodejs": "/shield/reference?f=node-js", - "/shield/reference/sveltekit": "/shield/reference?f=sveltekit", + "/get-started/bun": "/sdk/bun/get-started/", + "/get-started/nextjs": "/sdk/next/get-started/", + "/get-started/nodejs": "/sdk/node/get-started/", + "/get-started/fastify": "/sdk/fastify/get-started/", + "/get-started/sveltekit": "/sdk/sveltekit/get-started/", + "/shield/quick-start/bun": "/sdk/bun/shield/quick-start/", + "/shield/quick-start/nextjs": "/sdk/next/shield/quick-start/", + "/shield/quick-start/nodejs": "/sdk/node/shield/quick-start/", + "/shield/quick-start/sveltekit": "/sdk/sveltekit/shield/quick-start/", + "/shield/reference/bun": "/sdk/bun/shield/reference/", + "/shield/reference/nextjs": "/sdk/next/shield/reference/", + "/shield/reference/nodejs": "/sdk/node/shield/reference/", + "/shield/reference/sveltekit": "/sdk/sveltekit/shield/reference/", "/rate-limiting/concepts": "/rate-limiting", - "/rate-limiting/quick-start/bun": "/rate-limiting/quick-start?f=bun", - "/rate-limiting/quick-start/nextjs": "/rate-limiting/quick-start?f=next-js", - "/rate-limiting/quick-start/nodejs": "/rate-limiting/quick-start?f=node-js", + "/rate-limiting/quick-start/bun": "/sdk/bun/rate-limiting/quick-start/", + "/rate-limiting/quick-start/nextjs": + "/sdk/next/rate-limiting/quick-start/", + "/rate-limiting/quick-start/nodejs": + "/sdk/node/rate-limiting/quick-start/", "/rate-limiting/quick-start/sveltekit": - "/rate-limiting/quick-start?f=sveltekit", - "/bot-protection/quick-start/bun": "/bot-protection/quick-start?f=bun", + "/sdk/sveltekit/rate-limiting/quick-start/", + "/bot-protection/quick-start/bun": "/sdk/bun/bot-protection/quick-start/", "/bot-protection/quick-start/nextjs": - "/bot-protection/quick-start?f=next-js", + "/sdk/next/bot-protection/quick-start/", "/bot-protection/quick-start/nodejs": - "/bot-protection/quick-start?f=node-js", + "/sdk/node/bot-protection/quick-start/", "/bot-protection/quick-start/sveltekit": - "/bot-protection/quick-start?f=sveltekit", - "/bot-protection/reference/bun": "/bot-protection/reference?f=bun", - "/bot-protection/reference/nextjs": "/bot-protection/reference?f=next-js", - "/bot-protection/reference/nodejs": "/bot-protection/reference?f=node-js", + "/sdk/sveltekit/bot-protection/quick-start/", + "/bot-protection/reference/bun": "/sdk/bun/bot-protection/reference/", + "/bot-protection/reference/nextjs": "/sdk/next/bot-protection/reference/", + "/bot-protection/reference/nodejs": "/sdk/node/bot-protection/reference/", "/bot-protection/reference/sveltekit": - "/bot-protection/reference?f=sveltekit", - "/email-validation/quick-start/bun": "/email-validation/quick-start?f=bun", + "/sdk/sveltekit/bot-protection/reference/", + "/email-validation/quick-start/bun": + "/sdk/bun/email-validation/quick-start/", "/email-validation/quick-start/nextjs": - "/email-validation/quick-start?f=next-js", + "/sdk/next/email-validation/quick-start/", "/email-validation/quick-start/nodejs": - "/email-validation/quick-start?f=node-js", + "/sdk/node/email-validation/quick-start/", "/email-validation/quick-start/sveltekit": - "/email-validation/quick-start?f=sveltekit", - "/email-validation/reference/bun": "/email-validation/reference?f=bun", + "/sdk/sveltekit/email-validation/quick-start/", + "/email-validation/reference/bun": "/sdk/bun/email-validation/reference/", "/email-validation/reference/nextjs": - "/email-validation/reference?f=next-js", + "/sdk/next/email-validation/reference/", "/email-validation/reference/nodejs": - "/email-validation/reference?f=node-js", + "/sdk/node/email-validation/reference/", "/email-validation/reference/sveltekit": - "/email-validation/reference?f=sveltekit", + "/sdk/sveltekit/email-validation/reference/", "/filters/concepts": "/filters", "/signup-protection/quick-start/bun": - "/signup-protection/quick-start?f=bun", + "/sdk/bun/signup-protection/quick-start/", "/signup-protection/quick-start/nextjs": - "/signup-protection/quick-start?f=next-js", + "/sdk/next/signup-protection/quick-start/", "/signup-protection/quick-start/nodejs": - "/signup-protection/quick-start?f=node-js", + "/sdk/node/signup-protection/quick-start/", "/signup-protection/quick-start/sveltekit": - "/signup-protection/quick-start?f=sveltekit", - "/signup-protection/reference/bun": "/signup-protection/reference?f=bun", + "/sdk/sveltekit/signup-protection/quick-start/", + "/signup-protection/reference/bun": "/sdk/bun/signup-protection/reference/", "/signup-protection/reference/nextjs": - "/signup-protection/reference?f=next-js", + "/sdk/next/signup-protection/reference/", "/signup-protection/reference/nodejs": - "/signup-protection/reference?f=node-js", + "/sdk/node/signup-protection/reference/", "/signup-protection/reference/sveltekit": - "/signup-protection/reference?f=sveltekit", + "/sdk/sveltekit/signup-protection/reference/", "/reference/ts-js": "/reference/nodejs", "/bot-protection/bot-types": "/bot-protection/identifying-bots", "/mcp": "/mcp-server", @@ -296,6 +304,7 @@ export default defineConfig({ // local preview. Keep both in sync. Real file is sitemap-index.xml; // do not collapse /sdk/{framework}/ sitemap entries. "/sitemap.xml": "/sitemap-index.xml", + ...variantOnlySdkAstroRedirects(), ...withComparisonSdkScopes(comparisonMarketingRedirects), }, }); diff --git a/package.json b/package.json index 16d66d50d..59a1cfb83 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,8 @@ "preview": "astro preview", "pw:install": "playwright install --with-deps chromium --only-shell", "pw:open": "playwright test --ui-host=0.0.0.0", - "pw:run": "playwright test" + "pw:run": "playwright test", + "generate:legacy-f-redirects": "node --experimental-strip-types scripts/generate-legacy-f-redirects.ts" }, "type": "module", "version": "0.0.1", diff --git a/public/llms-full.txt b/public/llms-full.txt index 36c698dad..48552a6d3 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -156,24 +156,25 @@ Go SDK: https://github.com/arcjet/arcjet-go ## Quick start – choose your framework -Each link below directs to the quick start guide with a framework-specific view: +Each link below directs to the SDK-scoped quick start guide for that framework: -- [Astro quick start](https://docs.arcjet.com/get-started?f=astro) -- [Bun quick start](https://docs.arcjet.com/get-started?f=bun) -- [Deno quick start](https://docs.arcjet.com/get-started?f=deno) -- [Fastify quick start](https://docs.arcjet.com/get-started?f=fastify) +- [Astro quick start](https://docs.arcjet.com/sdk/astro/get-started/) +- [Bun quick start](https://docs.arcjet.com/sdk/bun/get-started/) +- [Deno quick start](https://docs.arcjet.com/sdk/deno/get-started/) +- [Fastify quick start](https://docs.arcjet.com/sdk/fastify/get-started/) - [Go SDK reference](https://docs.arcjet.com/reference/go) -- [NestJS quick start](https://docs.arcjet.com/get-started?f=nest-js) -- [Next.js quick start](https://docs.arcjet.com/get-started?f=next-js) -- [Node.js quick start](https://docs.arcjet.com/get-started?f=node-js) -- [Node.js + Express quick start](https://docs.arcjet.com/get-started?f=node-js-express) -- [Node.js + Hono quick start](https://docs.arcjet.com/get-started?f=node-js-hono) -- [Nuxt quick start](https://docs.arcjet.com/get-started?f=nuxt) -- [Python FastAPI quick start](https://docs.arcjet.com/get-started?f=python-fastapi) -- [Python Flask quick start](https://docs.arcjet.com/get-started?f=python-flask) -- [React Router quick start](https://docs.arcjet.com/get-started?f=react-router) -- [Remix quick start](https://docs.arcjet.com/get-started?f=remix) -- [SvelteKit quick start](https://docs.arcjet.com/get-started?f=sveltekit) +- [NestJS quick start](https://docs.arcjet.com/sdk/nest/get-started/) +- [Next.js quick start](https://docs.arcjet.com/sdk/next/get-started/) +- [Node.js quick start](https://docs.arcjet.com/sdk/node/get-started/) +- [Node.js + Express quick start](https://docs.arcjet.com/sdk/node/plus/express/get-started/) +- [Node.js + Hono quick start](https://docs.arcjet.com/sdk/node/plus/hono/get-started/) +- [Nuxt quick start](https://docs.arcjet.com/sdk/nuxt/get-started/) +- [Python + FastAPI quick start](https://docs.arcjet.com/sdk/python/plus/fastapi/get-started/) +- [Python + Flask quick start](https://docs.arcjet.com/sdk/python/plus/flask/get-started/) +- [React Router quick start](https://docs.arcjet.com/sdk/react-router/get-started/) +- [Remix quick start](https://docs.arcjet.com/sdk/remix/get-started/) +- [SvelteKit quick start](https://docs.arcjet.com/sdk/sveltekit/get-started/) +- [Bun + Hono quick start](https://docs.arcjet.com/sdk/bun/plus/hono/get-started/) Full docs: https://docs.arcjet.com diff --git a/public/llms.txt b/public/llms.txt index 250af34f1..99759d169 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -25,7 +25,28 @@ extra layer of defense. Pricing is based on usage, see https://arcjet.com/pricin - [MCP server](https://docs.arcjet.com/mcp-server): same management surface over MCP at `https://api.arcjet.com/mcp` (OAuth). - [Arcjet plugin](https://docs.arcjet.com/arcjet-plugin): bundled skills + MCP + coding rules for Claude Code and Cursor. - [Create an account](https://console.arcjet.com) -- [Quick start guides by framework](https://docs.arcjet.com/get-started) +- [Quick start guides by framework](https://docs.arcjet.com/get-started) (legacy; prefer the SDK-scoped URLs below) + +### SDK-scoped quick starts + +Use these stable URLs when linking to a framework-specific guide. They replace `?f=` query parameters and render the matching guide server-side. + +- [Next.js](https://docs.arcjet.com/sdk/next/get-started/) +- [Astro](https://docs.arcjet.com/sdk/astro/get-started/) +- [Bun](https://docs.arcjet.com/sdk/bun/get-started/) +- [Bun + Hono](https://docs.arcjet.com/sdk/bun/plus/hono/get-started/) +- [Deno](https://docs.arcjet.com/sdk/deno/get-started/) +- [Fastify](https://docs.arcjet.com/sdk/fastify/get-started/) +- [NestJS](https://docs.arcjet.com/sdk/nest/get-started/) +- [Node.js](https://docs.arcjet.com/sdk/node/get-started/) +- [Node.js + Express](https://docs.arcjet.com/sdk/node/plus/express/get-started/) +- [Node.js + Hono](https://docs.arcjet.com/sdk/node/plus/hono/get-started/) +- [Nuxt](https://docs.arcjet.com/sdk/nuxt/get-started/) +- [Python + FastAPI](https://docs.arcjet.com/sdk/python/plus/fastapi/get-started/) +- [Python + Flask](https://docs.arcjet.com/sdk/python/plus/flask/get-started/) +- [React Router](https://docs.arcjet.com/sdk/react-router/get-started/) +- [Remix](https://docs.arcjet.com/sdk/remix/get-started/) +- [SvelteKit](https://docs.arcjet.com/sdk/sveltekit/get-started/) ## Features diff --git a/scripts/generate-legacy-f-redirects.ts b/scripts/generate-legacy-f-redirects.ts new file mode 100644 index 000000000..4aafd6ce4 --- /dev/null +++ b/scripts/generate-legacy-f-redirects.ts @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import { + legacyFrameworkVercelRedirects, + variantOnlySdkVercelRedirects, +} from "../src/lib/sdk.ts"; + +const vercelPath = new URL("../vercel.json", import.meta.url); +const vercel = JSON.parse(fs.readFileSync(vercelPath, "utf8")) as { + redirects: Array<{ source?: string; has?: Array<{ key: string }> }>; +}; + +function isVariantOnlySdkRedirect(source: string | undefined): boolean { + return ( + source === "/sdk/python" || + source === "/sdk/python/:path((?!plus/).*)" + ); +} + +vercel.redirects = vercel.redirects.filter( + (redirect) => + !redirect.has?.some((condition) => condition.key === "f") && + !isVariantOnlySdkRedirect(redirect.source), +); + +const variantOnly = variantOnlySdkVercelRedirects(); +const generated = legacyFrameworkVercelRedirects(); +vercel.redirects.unshift(...variantOnly, ...generated); + +fs.writeFileSync(vercelPath, `${JSON.stringify(vercel, null, 2)}\n`); + +console.log( + `Updated vercel.json with ${variantOnly.length} variant-only SDK redirects and ${generated.length} legacy ?f= redirects`, +); diff --git a/src/components/FrameworkLinks.astro b/src/components/FrameworkLinks.astro new file mode 100644 index 000000000..17072af3e --- /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 framework link grid uses SDK routes. + */ +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 95% rename from src/components/FrameworkLinks.tsx rename to src/components/FrameworkLinksReact.tsx index b2e61e98b..06f4d6617 100644 --- a/src/components/FrameworkLinks.tsx +++ b/src/components/FrameworkLinksReact.tsx @@ -23,6 +23,7 @@ import { SvelteKit as IconSvelteKit } from "@/components/icons/tech/SvelteKit"; import { VercelAi as IconVercelAi } from "@/components/icons/tech/VercelAi"; import { VercelEve as IconVercelEve } from "@/components/icons/tech/VercelEve"; import { frameworks, getStoredFramework, type FrameworkKey } from "@/lib/prefs"; +import { hrefForLegacyFrameworkKey } from "@/lib/sdk"; import { queryParamFramework } from "@/store"; import { useStore } from "@nanostores/react"; import type { ForwardedRef, PropsWithChildren, ReactNode } from "react"; @@ -62,6 +63,14 @@ const FrameworkLinks = forwardRef( const $queryParamFramework = useStore(queryParamFramework); + const [basePath, setBasePath] = useState(path); + + useEffect(() => { + if (!path) { + setBasePath(window.location.pathname); + } + }, [path]); + useEffect(() => { // Check if a framework is set in query params const params = new URLSearchParams(window.location.search); @@ -182,7 +191,7 @@ const FrameworkLinks = forwardRef( key={f.key + idx} as="link" size="lg" - href={`${path}?f=${f.key}`} + href={hrefForLegacyFrameworkKey(f.key, basePath || path || "/")} decoratorLeft={icon} > {f.label} diff --git a/src/components/FrameworkSwitcher.tsx b/src/components/FrameworkSwitcher.tsx index 099804bb6..3ad807c34 100644 --- a/src/components/FrameworkSwitcher.tsx +++ b/src/components/FrameworkSwitcher.tsx @@ -33,6 +33,11 @@ import { isValidFrameworkKey, storeFramework, } from "@/lib/prefs"; +import { + docPathFromSdkPathname, + hrefForLegacyFrameworkKey, + sdkFromPathname, +} from "@/lib/sdk"; import { availableFrameworks, displayedFramework, @@ -111,10 +116,12 @@ const FrameworkSwitcher = forwardRef( storeFramework(val); - // Reload the page with the new framework in the query param - const url = new URL(window.location.toString()); - url.searchParams.set("f", val); - window.location.replace(url.toString()); + const currentPath = window.location.pathname; + const docPath = sdkFromPathname(currentPath) + ? docPathFromSdkPathname(currentPath) + : currentPath; + const target = hrefForLegacyFrameworkKey(val as FrameworkKey, docPath); + window.location.replace(target); }; // Sync store with current page frontmatter diff --git a/src/components/SdkHydration.astro b/src/components/SdkHydration.astro new file mode 100644 index 000000000..2d579bdcb --- /dev/null +++ b/src/components/SdkHydration.astro @@ -0,0 +1,18 @@ +--- +/** + * Seeds Astro island hydration on SDK routes that otherwise only contain + * static framework wrappers plus `SelectableContent` islands. + */ +import { sdkFromPathname } from "@/lib/sdk"; +import FrameworkName from "@/components/FrameworkName"; + +const isSdkRoute = sdkFromPathname(Astro.url.pathname) !== undefined; +--- + +{ + isSdkRoute ? ( + + ) : null +} diff --git a/src/components/SdkReferenceLinkByFramework.astro b/src/components/SdkReferenceLinkByFramework.astro index 657afcf5e..84cb1b40e 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"; --- - + > = {}; +for (const framework of frameworks) { + if (Astro.slots.has(framework.key)) { + frameworkSlots[framework.key] = await Astro.slots.render(framework.key); + } +} +if (Astro.slots.has("default")) { + frameworkSlots.default = await Astro.slots.render("default"); +} +--- + +{ + sdkKey && hasMatchingSlot ? ( + inline ? ( + + ) : ( +
+ +
+ ) + ) : ( + + ) +} diff --git a/src/components/SlotByFramework.tsx b/src/components/SlotByFrameworkReact.tsx similarity index 72% rename from src/components/SlotByFramework.tsx rename to src/components/SlotByFrameworkReact.tsx index 3c34ff17c..518061ba1 100644 --- a/src/components/SlotByFramework.tsx +++ b/src/components/SlotByFrameworkReact.tsx @@ -10,6 +10,8 @@ import "@/components/SlotByFramework.scss"; interface Props extends PropsWithChildren { inline?: boolean; + /** Pre-rendered framework slot HTML from the Astro wrapper. */ + frameworkSlots?: Partial>; } /** @@ -19,7 +21,7 @@ interface Props extends PropsWithChildren { * * @param inline - Renders the content without a wrapping element. */ -const SlotByFramework = ({ inline, ...props }: Props) => { +const SlotByFramework = ({ inline, frameworkSlots, ...props }: Props) => { const $displayedFramework = useStore(displayedFramework); // The selected framework @@ -30,13 +32,20 @@ const SlotByFramework = ({ inline, ...props }: Props) => { }, [$displayedFramework]); const content = useMemo(() => { + if (!selectedFramework) return null; + + const slottedHtml = frameworkSlots?.[selectedFramework]; + if (slottedHtml) { + return ; + } + return ( <> - {selectedFramework && extractSlotContent(props, selectedFramework)} + {extractSlotContent(props, selectedFramework)} {props.children} ); - }, [selectedFramework]); + }, [frameworkSlots, props, selectedFramework]); // Loading handling const [loading, setLoading] = useState(true); diff --git a/src/components/TextByFramework.astro b/src/components/TextByFramework.astro new file mode 100644 index 000000000..cecefacb8 --- /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.astro b/src/components/TitleByFramework.astro new file mode 100644 index 000000000..32ced3a5b --- /dev/null +++ b/src/components/TitleByFramework.astro @@ -0,0 +1,37 @@ +--- +/** + * TitleByFramework — Astro wrapper + * + * On SDK-scoped routes, renders the matching framework title statically with + * zero client JavaScript. On legacy routes, delegates to the React component. + */ +import type { FrameworkKey } from "@/lib/prefs"; +import TitleByFrameworkReact from "@/components/TitleByFramework"; +import { sdkFromPathname, legacyKeyFromPathname } from "@/lib/sdk"; +import { kebabToCamel } from "@/lib/utils"; + +type Props = { [key in FrameworkKey]?: string } & { + default?: string; +}; + +const props = Astro.props as Props; +const sdkKey = sdkFromPathname(Astro.url.pathname); + +let resolvedTitle: string | undefined; +if (sdkKey) { + const legacyKey = legacyKeyFromPathname(Astro.url.pathname); + if (legacyKey) { + const camelKey = kebabToCamel(legacyKey) as FrameworkKey; + resolvedTitle = + props[legacyKey] ?? props[camelKey] ?? props.default ?? undefined; + } +} +--- + +{ + sdkKey && resolvedTitle ? ( +

+ ) : ( + + ) +} diff --git a/src/components/TitleByFramework.tsx b/src/components/TitleByFramework.tsx index 4a77c9fbe..1af0ca449 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/components/link/ToSdk.astro b/src/components/link/ToSdk.astro index fab2385bb..7d5fc915d 100644 --- a/src/components/link/ToSdk.astro +++ b/src/components/link/ToSdk.astro @@ -11,8 +11,7 @@ type Props = HTMLAttributes<"a"> & { * The target SDK key to scope the link to e.g. `"node"`, `"next"`. * * On SDK-scoped pages this produces `/sdk/{sdk}/{path}`. On non-SDK pages - * it falls back to `{path}?f={legacyFrameworkKey}` for backwards - * compatibility. + * it produces the matching `/sdk/{sdk}/...` route when one exists. */ sdk: ArcjetSdkKey; }; diff --git a/src/components/overrides/MobileTableOfContents.astro b/src/components/overrides/MobileTableOfContents.astro index 5dbc35aba..4cf044fd6 100644 --- a/src/components/overrides/MobileTableOfContents.astro +++ b/src/components/overrides/MobileTableOfContents.astro @@ -1,16 +1,18 @@ --- import SdkIcon from "@/components/SdkIcon.astro"; import { -pathnameForSdk, sdk, +sdkDisplayLabelFromPathname, sdkFromPathname, -sdks, +sdkSwitcherOptions, +isSdkSwitcherOptionCurrent, } from "@/lib/sdk"; import { Icon } from "@astrojs/starlight/components"; import Default from "@astrojs/starlight/components/MobileTableOfContents.astro"; const activeSdkKey = sdkFromPathname(Astro.url.pathname); const activeSdk = activeSdkKey ? sdk(activeSdkKey) : undefined; +const activeSdkLabel = sdkDisplayLabelFromPathname(Astro.url.pathname); --- {/* TODO: Remove once framework switching is removed. */} @@ -32,15 +34,17 @@ const activeSdk = activeSdkKey ? sdk(activeSdkKey) : undefined; type="button" > - {activeSdk.label} + {activeSdkLabel}