) {
+ return ;
+}
+
+export type { FumadocsLinkProps as LinkProps };
diff --git a/components/docskit/annotations/link.tsx b/components/docskit/annotations/link.tsx
index b309b11e9..79322ca1a 100644
--- a/components/docskit/annotations/link.tsx
+++ b/components/docskit/annotations/link.tsx
@@ -1,5 +1,5 @@
import type { AnnotationHandler } from 'codehike/code';
-import Link from 'next/link';
+import { DocsLink as Link } from '@/components/docs-link';
export const link: AnnotationHandler = {
name: 'link',
diff --git a/components/docskit/components.tsx b/components/docskit/components.tsx
index 801018b5b..9c23b68a0 100644
--- a/components/docskit/components.tsx
+++ b/components/docskit/components.tsx
@@ -1,7 +1,7 @@
import { Block, CodeBlock } from 'codehike/blocks';
import type { RawCode } from 'codehike/code';
-import Link from 'fumadocs-core/link';
import { z } from 'zod';
+import { DocsFumadocsLink as Link } from '@/components/docs-link';
import { Code } from './code';
import { SimpleCode } from './code-simple';
import { InlineCode } from './inline-code';
diff --git a/components/feedback/feedback-wrapper.tsx b/components/feedback/feedback-wrapper.tsx
index 2a3a73678..7e5f0d1e7 100644
--- a/components/feedback/feedback-wrapper.tsx
+++ b/components/feedback/feedback-wrapper.tsx
@@ -2,6 +2,7 @@
import { ThumbsDown, ThumbsUp } from 'lucide-react';
import { useCallback, useState } from 'react';
+import { docsPath, docsUrl } from '@/lib/docs-path';
import { cn } from '@/lib/utils';
import type { FeedbackResponse } from '@/types/feedback';
@@ -25,12 +26,9 @@ export function FeedbackWrapper({ pageTitle, pagePath, className }: FeedbackWrap
setError(null);
try {
- const pageUrl =
- typeof window !== 'undefined'
- ? window.location.href
- : `https://docs.steel.dev${pagePath}`;
+ const pageUrl = typeof window !== 'undefined' ? window.location.href : docsUrl(pagePath);
- const response = await fetch('/api/feedback', {
+ const response = await fetch(docsPath('/api/feedback'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/components/home/llms-hint-card.tsx b/components/home/llms-hint-card.tsx
index 976f8a74c..8316841ef 100644
--- a/components/home/llms-hint-card.tsx
+++ b/components/home/llms-hint-card.tsx
@@ -4,9 +4,10 @@
import { Check, Copy, Sparkles } from 'lucide-react';
import * as React from 'react';
+import { docsUrl } from '@/lib/docs-path';
import { cn } from '@/lib/utils';
-const LLMS_URL = 'https://docs.steel.dev/llms-full.txt';
+const LLMS_URL = docsUrl('/llms-full.txt');
export function LlmsHintCard() {
const [copied, setCopied] = React.useState(false);
@@ -20,7 +21,8 @@ export function LlmsHintCard() {
Building with an AI agent?
- Point your coding agent to docs.steel.dev/llms-full.txt for a fast start with Steel.
+ Point your coding agent to {LLMS_URL.replace('https://', '')} for a fast start with
+ Steel.
{
e.preventDefault();
e.stopPropagation();
- window.location.href = href;
+ window.location.href = docsPath(href);
}}
type="button"
className="hover:no-underline"
diff --git a/components/layout/docs/page-client.tsx b/components/layout/docs/page-client.tsx
index cb48924e1..fbceed086 100644
--- a/components/layout/docs/page-client.tsx
+++ b/components/layout/docs/page-client.tsx
@@ -2,7 +2,6 @@
import { type BreadcrumbOptions, getBreadcrumbItemsFromPath } from 'fumadocs-core/breadcrumb';
import { createContext, usePathname } from 'fumadocs-core/framework';
-import Link from 'fumadocs-core/link';
import type { PageTree } from 'fumadocs-core/server';
import { useActiveAnchor } from 'fumadocs-core/toc';
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
@@ -12,6 +11,7 @@ import { useSidebar } from 'fumadocs-ui/contexts/sidebar';
import { useTreeContext, useTreePath } from 'fumadocs-ui/contexts/tree';
import { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
import { type ComponentProps, Fragment, useEffect, useMemo, useRef, useState } from 'react';
+import { DocsFumadocsLink as Link } from '@/components/docs-link';
import { cn } from '../../../lib/cn';
import { isActive } from '../../../lib/is-active';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../ui/collapsible';
diff --git a/components/layout/mobile-navigation.tsx b/components/layout/mobile-navigation.tsx
index 50f355d9b..eb2f0662b 100644
--- a/components/layout/mobile-navigation.tsx
+++ b/components/layout/mobile-navigation.tsx
@@ -3,10 +3,10 @@
import type { PageTree } from 'fumadocs-core/server';
import { TreeContextProvider } from 'fumadocs-ui/contexts/tree';
import { ChevronRight, X } from 'lucide-react';
-import Link from 'next/link';
import { usePathname } from 'next/navigation';
import React, { useState } from 'react';
import { baseOptions } from '@/app/layout.config';
+import { DocsLink as Link } from '@/components/docs-link';
import { Sidebar } from '@/components/layouts/docs';
import { isNavItemActive } from '@/components/layouts/links';
import {
diff --git a/components/layout/nav.tsx b/components/layout/nav.tsx
index 7a809dfa9..41ddcd94e 100644
--- a/components/layout/nav.tsx
+++ b/components/layout/nav.tsx
@@ -1,6 +1,6 @@
'use client';
-import Link, { type LinkProps } from 'fumadocs-core/link';
import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from 'react';
+import { DocsFumadocsLink as Link, type LinkProps } from '@/components/docs-link';
import { cn } from '../../lib/utils';
export interface NavProviderProps {
diff --git a/components/layout/root-toggle.tsx b/components/layout/root-toggle.tsx
index f078553d6..6145e0a42 100644
--- a/components/layout/root-toggle.tsx
+++ b/components/layout/root-toggle.tsx
@@ -1,9 +1,9 @@
'use client';
import { useSidebar } from 'fumadocs-ui/provider';
import { ChevronsUpDown } from 'lucide-react';
-import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { type HTMLAttributes, type ReactNode, useMemo, useState } from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
import { isActive } from '@/lib/is-active';
import { cn } from '@/lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
diff --git a/components/layouts/docs.tsx b/components/layouts/docs.tsx
index 32c28980c..13bd00b38 100644
--- a/components/layouts/docs.tsx
+++ b/components/layouts/docs.tsx
@@ -3,14 +3,15 @@
import { cva } from 'class-variance-authority';
import { CopyLLMSButton } from 'components/llmsbutton';
import { usePathname } from 'fumadocs-core/framework';
-import Link from 'fumadocs-core/link';
import type { PageTree } from 'fumadocs-core/server';
import { useSidebar } from 'fumadocs-ui/contexts/sidebar';
import { TreeContextProvider, useTreeContext } from 'fumadocs-ui/contexts/tree';
import { ArrowUpRight, ChevronDown, ChevronRight, SidebarIcon } from 'lucide-react';
import React, { type ButtonHTMLAttributes, type ReactNode, useMemo } from 'react';
+import { DocsFumadocsLink as Link } from '@/components/docs-link';
import { MobileMenuProvider } from '@/contexts/mobile-menu';
import { useLocalizedNavigation } from '@/hooks/use-localized-navigation';
+import { stripDocsPath } from '@/lib/docs-path';
import { cn } from '@/lib/utils';
import { MobileMenuButton } from '../layout/mobile-menu-button';
import { SearchToggle } from '../layout/search-toggle';
@@ -184,7 +185,7 @@ export function Sidebar() {
const children = useMemo(() => {
const filterCriteria = ['overview', 'integrations', 'cookbook', 'changelog'];
- const sectionPathname = pathname === '/' ? '/overview' : pathname;
+ const sectionPathname = stripDocsPath(pathname) === '/' ? '/overview' : stripDocsPath(pathname);
const shouldFilterItem = (item: PageTree.Node, owningSection?: string): boolean => {
// Link nodes from meta.json `[Label](url)` syntax — both external
diff --git a/components/layouts/interactive.tsx b/components/layouts/interactive.tsx
index 0989380fb..9813b714e 100644
--- a/components/layouts/interactive.tsx
+++ b/components/layouts/interactive.tsx
@@ -1,8 +1,8 @@
'use client';
import { Check } from 'lucide-react';
-import Link from 'next/link';
import type { ReactNode } from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
import { cn } from '@/lib/utils';
import { usePageData } from './page';
diff --git a/components/layouts/links.tsx b/components/layouts/links.tsx
index 1d2e8fd34..9a58c69c0 100644
--- a/components/layouts/links.tsx
+++ b/components/layouts/links.tsx
@@ -1,6 +1,5 @@
'use client';
import { usePathname } from 'fumadocs-core/framework';
-import Link from 'fumadocs-core/link';
import { ChevronDown } from 'lucide-react';
import React, {
type AnchorHTMLAttributes,
@@ -11,6 +10,7 @@ import React, {
useRef,
useState,
} from 'react';
+import { DocsFumadocsLink as Link } from '@/components/docs-link';
import { isActive } from '../../lib/is-active';
import { cn } from '../../lib/utils';
import {
diff --git a/components/llmsbutton.tsx b/components/llmsbutton.tsx
index fe2cd59f8..c47985bd3 100644
--- a/components/llmsbutton.tsx
+++ b/components/llmsbutton.tsx
@@ -1,11 +1,12 @@
import { Copy } from 'lucide-react';
import React, { useState } from 'react';
+import { docsPath } from '@/lib/docs-path';
export function CopyLLMSButton() {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
- const url = `${window.location.origin}/llms-full.txt`;
+ const url = `${window.location.origin}${docsPath('/llms-full.txt')}`;
navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
diff --git a/components/recipe-card.tsx b/components/recipe-card.tsx
index 5c91b8c0f..9b6bae11d 100644
--- a/components/recipe-card.tsx
+++ b/components/recipe-card.tsx
@@ -1,5 +1,5 @@
-import Link from 'next/link';
import type { ReactNode, SVGProps } from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
import { GoIcon, PythonIcon, RustIcon, TSIcon } from '@/components/ui/icon';
import { cn } from '@/lib/utils';
diff --git a/components/recipe-carousel.tsx b/components/recipe-carousel.tsx
index afebf1d2e..8e1133fd8 100644
--- a/components/recipe-carousel.tsx
+++ b/components/recipe-carousel.tsx
@@ -1,7 +1,7 @@
'use client';
-import Link from 'next/link';
import * as React from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import {
diff --git a/components/recipe-meta.tsx b/components/recipe-meta.tsx
index ce0940c8c..3b07937d6 100644
--- a/components/recipe-meta.tsx
+++ b/components/recipe-meta.tsx
@@ -1,5 +1,5 @@
import { Github } from 'lucide-react';
-import Link from 'next/link';
+import { DocsLink as Link } from '@/components/docs-link';
interface AuthorMeta {
handle: string;
diff --git a/components/recipe-quickstart.tsx b/components/recipe-quickstart.tsx
index 388b12b95..559850967 100644
--- a/components/recipe-quickstart.tsx
+++ b/components/recipe-quickstart.tsx
@@ -1,8 +1,8 @@
'use client';
import { Check, Copy } from 'lucide-react';
-import Link from 'next/link';
import { useState } from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
interface Props {
// CLI-facing identifier (basename of registry.yaml's `path`, e.g.
diff --git a/components/search-dialog.tsx b/components/search-dialog.tsx
index 8761e4f38..7dd235863 100644
--- a/components/search-dialog.tsx
+++ b/components/search-dialog.tsx
@@ -26,6 +26,7 @@ import {
CommandSeparator,
} from '@/components/ui/command';
import { useSearch } from '@/hooks/use-search';
+import { docsPath } from '@/lib/docs-path';
const stopWords = new Set([
'a',
@@ -302,7 +303,7 @@ export default function SearchDialog() {
query: currentQuery,
} = useDocsSearch({
type: 'fetch',
- api: '/api/search',
+ api: docsPath('/api/search'),
});
const [pendingQuery, setPendingQuery] = React.useState('');
@@ -358,11 +359,11 @@ export default function SearchDialog() {
callback();
};
const goToDoc = (slug: string) => {
- router.push(slug);
+ router.push(docsPath(slug));
handleOpenChange(false);
};
const goToHref = (href: string) => {
- router.push(href);
+ router.push(docsPath(href));
handleOpenChange(false);
};
const handleAiAction = (actionId: string) => {
diff --git a/components/skill-grid.tsx b/components/skill-grid.tsx
index 28f06197a..8dade6acb 100644
--- a/components/skill-grid.tsx
+++ b/components/skill-grid.tsx
@@ -1,4 +1,4 @@
-import Link from 'fumadocs-core/link';
+import { DocsFumadocsLink as Link } from '@/components/docs-link';
import { Badge } from '@/components/ui/badge';
const SKILLS = [
diff --git a/components/source-link.tsx b/components/source-link.tsx
index a5b7b412e..64513502f 100644
--- a/components/source-link.tsx
+++ b/components/source-link.tsx
@@ -1,5 +1,5 @@
import { Github } from 'lucide-react';
-import Link from 'next/link';
+import { DocsLink as Link } from '@/components/docs-link';
interface Props {
href: string;
diff --git a/components/ui/banner.tsx b/components/ui/banner.tsx
index 248544a71..08526c1b7 100644
--- a/components/ui/banner.tsx
+++ b/components/ui/banner.tsx
@@ -3,8 +3,8 @@
import { cva } from 'class-variance-authority';
import { isWithinInterval, parseISO } from 'date-fns';
import { X } from 'lucide-react';
-import Link from 'next/link';
import { type HTMLAttributes, useCallback, useEffect, useState } from 'react';
+import { DocsLink as Link } from '@/components/docs-link';
import { cn } from '@/lib/utils';
import { Button } from './button';
diff --git a/docs-host-redirect/public/robots.txt b/docs-host-redirect/public/robots.txt
new file mode 100644
index 000000000..6a0ec374a
--- /dev/null
+++ b/docs-host-redirect/public/robots.txt
@@ -0,0 +1,6 @@
+# docs.steel.dev moved to https://steel.dev/docs. Each old URL redirects there.
+User-agent: *
+Allow: /
+
+Sitemap: https://docs.steel.dev/sitemap.xml
+Sitemap: https://steel.dev/docs/sitemap.xml
diff --git a/docs-host-redirect/public/sitemap.xml b/docs-host-redirect/public/sitemap.xml
new file mode 100644
index 000000000..62bcf0985
--- /dev/null
+++ b/docs-host-redirect/public/sitemap.xml
@@ -0,0 +1,190 @@
+
+
+https://docs.steel.dev/
+https://docs.steel.dev/changelog/changelog-000
+https://docs.steel.dev/changelog/changelog-001
+https://docs.steel.dev/changelog/changelog-002
+https://docs.steel.dev/changelog/changelog-003
+https://docs.steel.dev/changelog/changelog-004
+https://docs.steel.dev/changelog/changelog-005
+https://docs.steel.dev/changelog/changelog-006
+https://docs.steel.dev/changelog/changelog-007
+https://docs.steel.dev/changelog/changelog-008
+https://docs.steel.dev/changelog/changelog-009
+https://docs.steel.dev/changelog/changelog-010
+https://docs.steel.dev/changelog/changelog-011
+https://docs.steel.dev/changelog/changelog-012
+https://docs.steel.dev/changelog/changelog-013
+https://docs.steel.dev/changelog/changelog-014
+https://docs.steel.dev/changelog/changelog-015
+https://docs.steel.dev/changelog/changelog-016
+https://docs.steel.dev/changelog/changelog-017
+https://docs.steel.dev/changelog/changelog-018
+https://docs.steel.dev/changelog/changelog-019
+https://docs.steel.dev/changelog/changelog-020
+https://docs.steel.dev/changelog/changelog-021
+https://docs.steel.dev/changelog/changelog-022
+https://docs.steel.dev/changelog/changelog-023
+https://docs.steel.dev/changelog/changelog-024
+https://docs.steel.dev/changelog/changelog-025
+https://docs.steel.dev/changelog/changelog-026
+https://docs.steel.dev/changelog/changelog-027
+https://docs.steel.dev/changelog/changelog-028
+https://docs.steel.dev/changelog/changelog-029
+https://docs.steel.dev/changelog/changelog-030
+https://docs.steel.dev/changelog/changelog-031
+https://docs.steel.dev/changelog/changelog-032
+https://docs.steel.dev/changelog/changelog-033
+https://docs.steel.dev/changelog/changelog-034
+https://docs.steel.dev/changelog/changelog-035
+https://docs.steel.dev/changelog/changelog-036
+https://docs.steel.dev/changelog/changelog-037
+https://docs.steel.dev/changelog/changelog-038
+https://docs.steel.dev/cookbook
+https://docs.steel.dev/cookbook/agentkit
+https://docs.steel.dev/cookbook/agno
+https://docs.steel.dev/cookbook/auth-context
+https://docs.steel.dev/cookbook/authors/aspectrr
+https://docs.steel.dev/cookbook/authors/bsparker
+https://docs.steel.dev/cookbook/authors/danew
+https://docs.steel.dev/cookbook/authors/hussufo
+https://docs.steel.dev/cookbook/authors/jagadeshjai
+https://docs.steel.dev/cookbook/authors/junhsss
+https://docs.steel.dev/cookbook/authors/nibzard
+https://docs.steel.dev/cookbook/browser-use
+https://docs.steel.dev/cookbook/browser-use-captcha-auto
+https://docs.steel.dev/cookbook/browser-use-captcha-manual
+https://docs.steel.dev/cookbook/chromedp
+https://docs.steel.dev/cookbook/chromiumoxide
+https://docs.steel.dev/cookbook/claude-agent-sdk
+https://docs.steel.dev/cookbook/claude-computer-use
+https://docs.steel.dev/cookbook/claude-computer-use-mobile
+https://docs.steel.dev/cookbook/convex-chat-with-page
+https://docs.steel.dev/cookbook/convex-price-watch
+https://docs.steel.dev/cookbook/credentials
+https://docs.steel.dev/cookbook/crewai
+https://docs.steel.dev/cookbook/deep-research
+https://docs.steel.dev/cookbook/eino
+https://docs.steel.dev/cookbook/extensions
+https://docs.steel.dev/cookbook/files
+https://docs.steel.dev/cookbook/gemini-computer-use
+https://docs.steel.dev/cookbook/genkit
+https://docs.steel.dev/cookbook/google-adk
+https://docs.steel.dev/cookbook/headless-chrome
+https://docs.steel.dev/cookbook/langchaingo
+https://docs.steel.dev/cookbook/langgraph
+https://docs.steel.dev/cookbook/magnitude
+https://docs.steel.dev/cookbook/mastra
+https://docs.steel.dev/cookbook/mcp
+https://docs.steel.dev/cookbook/microsoft-agent-framework
+https://docs.steel.dev/cookbook/notte
+https://docs.steel.dev/cookbook/openai-agents
+https://docs.steel.dev/cookbook/openai-computer-use
+https://docs.steel.dev/cookbook/playwright
+https://docs.steel.dev/cookbook/profiles
+https://docs.steel.dev/cookbook/puppeteer
+https://docs.steel.dev/cookbook/pydantic-ai
+https://docs.steel.dev/cookbook/restate-agent
+https://docs.steel.dev/cookbook/rig
+https://docs.steel.dev/cookbook/rod
+https://docs.steel.dev/cookbook/scrape
+https://docs.steel.dev/cookbook/selenium
+https://docs.steel.dev/cookbook/stagehand
+https://docs.steel.dev/cookbook/swiftide
+https://docs.steel.dev/cookbook/temporal-browser-workflow
+https://docs.steel.dev/cookbook/topics/agents
+https://docs.steel.dev/cookbook/topics/authentication
+https://docs.steel.dev/cookbook/topics/browser-automation
+https://docs.steel.dev/cookbook/topics/browser-use
+https://docs.steel.dev/cookbook/topics/captchas
+https://docs.steel.dev/cookbook/topics/computer-use
+https://docs.steel.dev/cookbook/topics/convex
+https://docs.steel.dev/cookbook/topics/mcp
+https://docs.steel.dev/cookbook/topics/mobile
+https://docs.steel.dev/cookbook/topics/nextjs
+https://docs.steel.dev/cookbook/topics/playwright
+https://docs.steel.dev/cookbook/topics/restate
+https://docs.steel.dev/cookbook/topics/search
+https://docs.steel.dev/cookbook/topics/steel-apis
+https://docs.steel.dev/cookbook/topics/subagents
+https://docs.steel.dev/cookbook/topics/temporal
+https://docs.steel.dev/cookbook/topics/triggerdev
+https://docs.steel.dev/cookbook/topics/typed-output
+https://docs.steel.dev/cookbook/trigger-dev-browser-job
+https://docs.steel.dev/cookbook/vercel-ai-sdk
+https://docs.steel.dev/cookbook/vercel-ai-sdk-nextjs
+https://docs.steel.dev/cookbook/you-com-search
+https://docs.steel.dev/integrations
+https://docs.steel.dev/integrations/agentkit
+https://docs.steel.dev/integrations/agno
+https://docs.steel.dev/integrations/ai-sdk
+https://docs.steel.dev/integrations/browser-use
+https://docs.steel.dev/integrations/claude-agent-sdk
+https://docs.steel.dev/integrations/claude-code
+https://docs.steel.dev/integrations/claude-computer-use
+https://docs.steel.dev/integrations/codex
+https://docs.steel.dev/integrations/crewai
+https://docs.steel.dev/integrations/gemini-computer-use
+https://docs.steel.dev/integrations/hermes-agent
+https://docs.steel.dev/integrations/langgraph
+https://docs.steel.dev/integrations/magnitude
+https://docs.steel.dev/integrations/mastra
+https://docs.steel.dev/integrations/notte
+https://docs.steel.dev/integrations/openai-agents-sdk
+https://docs.steel.dev/integrations/openai-computer-use
+https://docs.steel.dev/integrations/openclaw
+https://docs.steel.dev/integrations/pi-agent
+https://docs.steel.dev/integrations/playwright
+https://docs.steel.dev/integrations/puppeteer
+https://docs.steel.dev/integrations/pydantic-ai
+https://docs.steel.dev/integrations/replit
+https://docs.steel.dev/integrations/selenium
+https://docs.steel.dev/integrations/stackblitz-bolt.new
+https://docs.steel.dev/integrations/stagehand
+https://docs.steel.dev/integrations/x402
+https://docs.steel.dev/overview/agent-traces/api
+https://docs.steel.dev/overview/agent-traces/overview
+https://docs.steel.dev/overview/agent-traces/timeline
+https://docs.steel.dev/overview/authentication
+https://docs.steel.dev/overview/browser-tools/overview
+https://docs.steel.dev/overview/captchas-api/overview
+https://docs.steel.dev/overview/credentials-api/overview
+https://docs.steel.dev/overview/extensions-api/overview
+https://docs.steel.dev/overview/files-api/overview
+https://docs.steel.dev/overview/intro-to-steel
+https://docs.steel.dev/overview/legal
+https://docs.steel.dev/overview/need-help
+https://docs.steel.dev/overview/pricinglimits
+https://docs.steel.dev/overview/profiles-api/overview
+https://docs.steel.dev/overview/self-hosting/clustering
+https://docs.steel.dev/overview/self-hosting/docker
+https://docs.steel.dev/overview/self-hosting/extensions
+https://docs.steel.dev/overview/self-hosting/profiles
+https://docs.steel.dev/overview/self-hosting/railway
+https://docs.steel.dev/overview/self-hosting/steel-local-vs-steel-cloud
+https://docs.steel.dev/overview/self-hosting/webrtc
+https://docs.steel.dev/overview/sessions-api/configuration
+https://docs.steel.dev/overview/sessions-api/dedicated-ips
+https://docs.steel.dev/overview/sessions-api/embed-sessions
+https://docs.steel.dev/overview/sessions-api/embed-sessions/live-sessions
+https://docs.steel.dev/overview/sessions-api/embed-sessions/past-sessions
+https://docs.steel.dev/overview/sessions-api/fullscreen-mode
+https://docs.steel.dev/overview/sessions-api/human-in-the-loop
+https://docs.steel.dev/overview/sessions-api/mobile-mode
+https://docs.steel.dev/overview/sessions-api/overview
+https://docs.steel.dev/overview/sessions-api/quickstart
+https://docs.steel.dev/overview/sessions-api/reusing-auth-context
+https://docs.steel.dev/overview/sessions-api/session-lifecycle
+https://docs.steel.dev/overview/skills
+https://docs.steel.dev/overview/skills/available-skills
+https://docs.steel.dev/overview/skills/available-skills/steel-browser
+https://docs.steel.dev/overview/skills/available-skills/steel-developer
+https://docs.steel.dev/overview/skills/available-skills/steel-reliability
+https://docs.steel.dev/overview/skills/available-skills/steel-session-debugging
+https://docs.steel.dev/overview/skills/available-skills/steel-skill-creator
+https://docs.steel.dev/overview/skills/troubleshooting
+https://docs.steel.dev/overview/stealth/captcha-solving
+https://docs.steel.dev/overview/stealth/proxies
+https://docs.steel.dev/overview/steel-cli
+https://docs.steel.dev/overview/stripe-projects
+
diff --git a/docs-host-redirect/vercel.json b/docs-host-redirect/vercel.json
new file mode 100644
index 000000000..b0aa722aa
--- /dev/null
+++ b/docs-host-redirect/vercel.json
@@ -0,0 +1,3311 @@
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": null,
+ "installCommand": "",
+ "buildCommand": "",
+ "outputDirectory": "public",
+ "trailingSlash": false,
+ "ignoreCommand": "git diff --quiet HEAD^ HEAD -- .",
+ "redirects": [
+ {
+ "source": "/",
+ "destination": "https://steel.dev/docs",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/index.json",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/index.json",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/steel-browser.tar.gz",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/steel-browser.tar.gz",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/steel-developer.tar.gz",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/steel-developer.tar.gz",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/steel-reliability.tar.gz",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/steel-reliability.tar.gz",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/steel-session-debugging.tar.gz",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/steel-session-debugging.tar.gz",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agent-skills/steel-skill-creator.tar.gz",
+ "destination": "https://steel.dev/docs/.well-known/agent-skills/steel-skill-creator.tar.gz",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/agents.md",
+ "destination": "https://steel.dev/docs/AGENTS.md",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/api-catalog",
+ "destination": "https://steel.dev/docs/.well-known/api-catalog",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/design.md",
+ "destination": "https://steel.dev/docs/DESIGN.md",
+ "permanent": true
+ },
+ {
+ "source": "/.well-known/llms.txt",
+ "destination": "https://steel.dev/docs/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/21a10fefb8713c3f667a2e94460f9357.txt",
+ "destination": "https://steel.dev/docs/21a10fefb8713c3f667a2e94460f9357.txt",
+ "permanent": true
+ },
+ {
+ "source": "/AGENTS.md",
+ "destination": "https://steel.dev/docs/AGENTS.md",
+ "permanent": true
+ },
+ {
+ "source": "/DESIGN.md",
+ "destination": "https://steel.dev/docs/DESIGN.md",
+ "permanent": true
+ },
+ {
+ "source": "/api-reference",
+ "destination": "https://steel.apidocumentation.com/api-reference",
+ "permanent": true
+ },
+ {
+ "source": "/api/search",
+ "destination": "https://steel.dev/docs/api/search",
+ "permanent": true
+ },
+ {
+ "source": "/changelog",
+ "destination": "https://steel.dev/docs/changelog",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-000",
+ "destination": "https://steel.dev/docs/changelog/changelog-000",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-001",
+ "destination": "https://steel.dev/docs/changelog/changelog-001",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-002",
+ "destination": "https://steel.dev/docs/changelog/changelog-002",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-003",
+ "destination": "https://steel.dev/docs/changelog/changelog-003",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-004",
+ "destination": "https://steel.dev/docs/changelog/changelog-004",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-005",
+ "destination": "https://steel.dev/docs/changelog/changelog-005",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-006",
+ "destination": "https://steel.dev/docs/changelog/changelog-006",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-007",
+ "destination": "https://steel.dev/docs/changelog/changelog-007",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-008",
+ "destination": "https://steel.dev/docs/changelog/changelog-008",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-009",
+ "destination": "https://steel.dev/docs/changelog/changelog-009",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-010",
+ "destination": "https://steel.dev/docs/changelog/changelog-010",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-011",
+ "destination": "https://steel.dev/docs/changelog/changelog-011",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-012",
+ "destination": "https://steel.dev/docs/changelog/changelog-012",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-013",
+ "destination": "https://steel.dev/docs/changelog/changelog-013",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-014",
+ "destination": "https://steel.dev/docs/changelog/changelog-014",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-015",
+ "destination": "https://steel.dev/docs/changelog/changelog-015",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-016",
+ "destination": "https://steel.dev/docs/changelog/changelog-016",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-017",
+ "destination": "https://steel.dev/docs/changelog/changelog-017",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-018",
+ "destination": "https://steel.dev/docs/changelog/changelog-018",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-019",
+ "destination": "https://steel.dev/docs/changelog/changelog-019",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-020",
+ "destination": "https://steel.dev/docs/changelog/changelog-020",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-021",
+ "destination": "https://steel.dev/docs/changelog/changelog-021",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-022",
+ "destination": "https://steel.dev/docs/changelog/changelog-022",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-022.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-022.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-023",
+ "destination": "https://steel.dev/docs/changelog/changelog-023",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-023.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-023.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-024",
+ "destination": "https://steel.dev/docs/changelog/changelog-024",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-024.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-024.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-025",
+ "destination": "https://steel.dev/docs/changelog/changelog-025",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-025.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-025.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-026",
+ "destination": "https://steel.dev/docs/changelog/changelog-026",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-026.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-026.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-027",
+ "destination": "https://steel.dev/docs/changelog/changelog-027",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-027.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-027.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-028",
+ "destination": "https://steel.dev/docs/changelog/changelog-028",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-028.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-028.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-029",
+ "destination": "https://steel.dev/docs/changelog/changelog-029",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-029.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-029.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-030",
+ "destination": "https://steel.dev/docs/changelog/changelog-030",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-030.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-030.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-031",
+ "destination": "https://steel.dev/docs/changelog/changelog-031",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-031.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-031.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-032",
+ "destination": "https://steel.dev/docs/changelog/changelog-032",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-032.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-032.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-033",
+ "destination": "https://steel.dev/docs/changelog/changelog-033",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-033.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-033.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-034",
+ "destination": "https://steel.dev/docs/changelog/changelog-034",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-034.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-034.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-035",
+ "destination": "https://steel.dev/docs/changelog/changelog-035",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-035.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-035.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-036",
+ "destination": "https://steel.dev/docs/changelog/changelog-036",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-036.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-036.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-037",
+ "destination": "https://steel.dev/docs/changelog/changelog-037",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-037.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-037.md",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-038",
+ "destination": "https://steel.dev/docs/changelog/changelog-038",
+ "permanent": true
+ },
+ {
+ "source": "/changelog/changelog-038.md",
+ "destination": "https://steel.dev/docs/changelog/changelog-038.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook",
+ "destination": "https://steel.dev/docs/cookbook",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook.md",
+ "destination": "https://steel.dev/docs/cookbook.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/agentkit",
+ "destination": "https://steel.dev/docs/cookbook/agentkit",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/agentkit.md",
+ "destination": "https://steel.dev/docs/cookbook/agentkit.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/agno",
+ "destination": "https://steel.dev/docs/cookbook/agno",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/agno.md",
+ "destination": "https://steel.dev/docs/cookbook/agno.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/auth-context",
+ "destination": "https://steel.dev/docs/cookbook/auth-context",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/auth-context-starter",
+ "destination": "https://steel.dev/docs/cookbook/auth-context",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/auth-context.md",
+ "destination": "https://steel.dev/docs/cookbook/auth-context.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/aspectrr",
+ "destination": "https://steel.dev/docs/cookbook/authors/aspectrr",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/bsparker",
+ "destination": "https://steel.dev/docs/cookbook/authors/bsparker",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/danew",
+ "destination": "https://steel.dev/docs/cookbook/authors/danew",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/hussufo",
+ "destination": "https://steel.dev/docs/cookbook/authors/hussufo",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/jagadeshjai",
+ "destination": "https://steel.dev/docs/cookbook/authors/jagadeshjai",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/junhsss",
+ "destination": "https://steel.dev/docs/cookbook/authors/junhsss",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/authors/nibzard",
+ "destination": "https://steel.dev/docs/cookbook/authors/nibzard",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use",
+ "destination": "https://steel.dev/docs/cookbook/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use-captcha-auto",
+ "destination": "https://steel.dev/docs/cookbook/browser-use-captcha-auto",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use-captcha-auto.md",
+ "destination": "https://steel.dev/docs/cookbook/browser-use-captcha-auto.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use-captcha-manual",
+ "destination": "https://steel.dev/docs/cookbook/browser-use-captcha-manual",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use-captcha-manual.md",
+ "destination": "https://steel.dev/docs/cookbook/browser-use-captcha-manual.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/browser-use.md",
+ "destination": "https://steel.dev/docs/cookbook/browser-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/chromedp",
+ "destination": "https://steel.dev/docs/cookbook/chromedp",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/chromedp.md",
+ "destination": "https://steel.dev/docs/cookbook/chromedp.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/chromiumoxide",
+ "destination": "https://steel.dev/docs/cookbook/chromiumoxide",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/chromiumoxide.md",
+ "destination": "https://steel.dev/docs/cookbook/chromiumoxide.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-agent-sdk",
+ "destination": "https://steel.dev/docs/cookbook/claude-agent-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-agent-sdk.md",
+ "destination": "https://steel.dev/docs/cookbook/claude-agent-sdk.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use-mobile",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use-mobile",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use-mobile.md",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use-mobile.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use-py",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use-ts",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/claude-computer-use.md",
+ "destination": "https://steel.dev/docs/cookbook/claude-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/convex-chat-with-page",
+ "destination": "https://steel.dev/docs/cookbook/convex-chat-with-page",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/convex-chat-with-page.md",
+ "destination": "https://steel.dev/docs/cookbook/convex-chat-with-page.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/convex-price-watch",
+ "destination": "https://steel.dev/docs/cookbook/convex-price-watch",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/convex-price-watch.md",
+ "destination": "https://steel.dev/docs/cookbook/convex-price-watch.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/credentials",
+ "destination": "https://steel.dev/docs/cookbook/credentials",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/credentials-starter",
+ "destination": "https://steel.dev/docs/cookbook/credentials",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/credentials.md",
+ "destination": "https://steel.dev/docs/cookbook/credentials.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/crewai",
+ "destination": "https://steel.dev/docs/cookbook/crewai",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/crewai.md",
+ "destination": "https://steel.dev/docs/cookbook/crewai.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/deep-research",
+ "destination": "https://steel.dev/docs/cookbook/deep-research",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/deep-research.md",
+ "destination": "https://steel.dev/docs/cookbook/deep-research.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/eino",
+ "destination": "https://steel.dev/docs/cookbook/eino",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/eino.md",
+ "destination": "https://steel.dev/docs/cookbook/eino.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/extensions",
+ "destination": "https://steel.dev/docs/cookbook/extensions",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/extensions-starter",
+ "destination": "https://steel.dev/docs/cookbook/extensions",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/extensions.md",
+ "destination": "https://steel.dev/docs/cookbook/extensions.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/files",
+ "destination": "https://steel.dev/docs/cookbook/files",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/files-api",
+ "destination": "https://steel.dev/docs/cookbook/files",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/files-starter",
+ "destination": "https://steel.dev/docs/cookbook/files",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/files.md",
+ "destination": "https://steel.dev/docs/cookbook/files.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/gemini-computer-use",
+ "destination": "https://steel.dev/docs/cookbook/gemini-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/gemini-computer-use-py",
+ "destination": "https://steel.dev/docs/cookbook/gemini-computer-use#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/gemini-computer-use-ts",
+ "destination": "https://steel.dev/docs/cookbook/gemini-computer-use#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/gemini-computer-use.md",
+ "destination": "https://steel.dev/docs/cookbook/gemini-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/genkit",
+ "destination": "https://steel.dev/docs/cookbook/genkit",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/genkit.md",
+ "destination": "https://steel.dev/docs/cookbook/genkit.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/google-adk",
+ "destination": "https://steel.dev/docs/cookbook/google-adk",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/google-adk.md",
+ "destination": "https://steel.dev/docs/cookbook/google-adk.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/headless-chrome",
+ "destination": "https://steel.dev/docs/cookbook/headless-chrome",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/headless-chrome.md",
+ "destination": "https://steel.dev/docs/cookbook/headless-chrome.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/langchaingo",
+ "destination": "https://steel.dev/docs/cookbook/langchaingo",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/langchaingo.md",
+ "destination": "https://steel.dev/docs/cookbook/langchaingo.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/langgraph",
+ "destination": "https://steel.dev/docs/cookbook/langgraph",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/langgraph.md",
+ "destination": "https://steel.dev/docs/cookbook/langgraph.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/llms.txt",
+ "destination": "https://steel.dev/docs/cookbook/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/magnitude",
+ "destination": "https://steel.dev/docs/cookbook/magnitude",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/magnitude.md",
+ "destination": "https://steel.dev/docs/cookbook/magnitude.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/mastra",
+ "destination": "https://steel.dev/docs/cookbook/mastra",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/mastra.md",
+ "destination": "https://steel.dev/docs/cookbook/mastra.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/mcp",
+ "destination": "https://steel.dev/docs/cookbook/mcp",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/mcp.md",
+ "destination": "https://steel.dev/docs/cookbook/mcp.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/microsoft-agent-framework",
+ "destination": "https://steel.dev/docs/cookbook/microsoft-agent-framework",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/microsoft-agent-framework.md",
+ "destination": "https://steel.dev/docs/cookbook/microsoft-agent-framework.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/notte",
+ "destination": "https://steel.dev/docs/cookbook/notte",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/notte.md",
+ "destination": "https://steel.dev/docs/cookbook/notte.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-agents",
+ "destination": "https://steel.dev/docs/cookbook/openai-agents",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-agents-py",
+ "destination": "https://steel.dev/docs/cookbook/openai-agents#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-agents-ts",
+ "destination": "https://steel.dev/docs/cookbook/openai-agents#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-agents.md",
+ "destination": "https://steel.dev/docs/cookbook/openai-agents.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-computer-use",
+ "destination": "https://steel.dev/docs/cookbook/openai-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-computer-use-py",
+ "destination": "https://steel.dev/docs/cookbook/openai-computer-use#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-computer-use-ts",
+ "destination": "https://steel.dev/docs/cookbook/openai-computer-use#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/openai-computer-use.md",
+ "destination": "https://steel.dev/docs/cookbook/openai-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/playwright",
+ "destination": "https://steel.dev/docs/cookbook/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/playwright-py",
+ "destination": "https://steel.dev/docs/cookbook/playwright#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/playwright-python",
+ "destination": "https://steel.dev/docs/cookbook/playwright#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/playwright-ts",
+ "destination": "https://steel.dev/docs/cookbook/playwright#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/playwright.md",
+ "destination": "https://steel.dev/docs/cookbook/playwright.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/profiles",
+ "destination": "https://steel.dev/docs/cookbook/profiles",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/profiles.md",
+ "destination": "https://steel.dev/docs/cookbook/profiles.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/puppeteer",
+ "destination": "https://steel.dev/docs/cookbook/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/puppeteer-ts",
+ "destination": "https://steel.dev/docs/cookbook/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/puppeteer.md",
+ "destination": "https://steel.dev/docs/cookbook/puppeteer.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/pydantic-ai",
+ "destination": "https://steel.dev/docs/cookbook/pydantic-ai",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/pydantic-ai.md",
+ "destination": "https://steel.dev/docs/cookbook/pydantic-ai.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/restate-agent",
+ "destination": "https://steel.dev/docs/cookbook/restate-agent",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/restate-agent.md",
+ "destination": "https://steel.dev/docs/cookbook/restate-agent.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/rig",
+ "destination": "https://steel.dev/docs/cookbook/rig",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/rig.md",
+ "destination": "https://steel.dev/docs/cookbook/rig.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/rod",
+ "destination": "https://steel.dev/docs/cookbook/rod",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/rod.md",
+ "destination": "https://steel.dev/docs/cookbook/rod.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/scrape",
+ "destination": "https://steel.dev/docs/cookbook/scrape",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/scrape.md",
+ "destination": "https://steel.dev/docs/cookbook/scrape.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/selenium",
+ "destination": "https://steel.dev/docs/cookbook/selenium",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/selenium.md",
+ "destination": "https://steel.dev/docs/cookbook/selenium.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/stagehand",
+ "destination": "https://steel.dev/docs/cookbook/stagehand",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/stagehand-py",
+ "destination": "https://steel.dev/docs/cookbook/stagehand#python",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/stagehand-ts",
+ "destination": "https://steel.dev/docs/cookbook/stagehand#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/stagehand.md",
+ "destination": "https://steel.dev/docs/cookbook/stagehand.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/steel-browser-use-starter",
+ "destination": "https://steel.dev/docs/cookbook/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/swiftide",
+ "destination": "https://steel.dev/docs/cookbook/swiftide",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/swiftide.md",
+ "destination": "https://steel.dev/docs/cookbook/swiftide.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/temporal-browser-workflow",
+ "destination": "https://steel.dev/docs/cookbook/temporal-browser-workflow",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/temporal-browser-workflow.md",
+ "destination": "https://steel.dev/docs/cookbook/temporal-browser-workflow.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/agents",
+ "destination": "https://steel.dev/docs/cookbook/topics/agents",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/agents.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/agents.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/authentication",
+ "destination": "https://steel.dev/docs/cookbook/topics/authentication",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/authentication.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/authentication.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/browser-automation",
+ "destination": "https://steel.dev/docs/cookbook/topics/browser-automation",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/browser-automation.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/browser-automation.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/browser-use",
+ "destination": "https://steel.dev/docs/cookbook/topics/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/browser-use.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/browser-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/captchas",
+ "destination": "https://steel.dev/docs/cookbook/topics/captchas",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/captchas.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/captchas.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/computer-use",
+ "destination": "https://steel.dev/docs/cookbook/topics/computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/computer-use.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/convex",
+ "destination": "https://steel.dev/docs/cookbook/topics/convex",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/convex.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/convex.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/llms.txt",
+ "destination": "https://steel.dev/docs/cookbook/topics/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/mcp",
+ "destination": "https://steel.dev/docs/cookbook/topics/mcp",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/mcp.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/mcp.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/mobile",
+ "destination": "https://steel.dev/docs/cookbook/topics/mobile",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/mobile.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/mobile.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/nextjs",
+ "destination": "https://steel.dev/docs/cookbook/topics/nextjs",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/nextjs.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/nextjs.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/playwright",
+ "destination": "https://steel.dev/docs/cookbook/topics/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/playwright.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/playwright.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/restate",
+ "destination": "https://steel.dev/docs/cookbook/topics/restate",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/restate.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/restate.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/search",
+ "destination": "https://steel.dev/docs/cookbook/topics/search",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/search.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/search.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/steel-apis",
+ "destination": "https://steel.dev/docs/cookbook/topics/steel-apis",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/steel-apis.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/steel-apis.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/subagents",
+ "destination": "https://steel.dev/docs/cookbook/topics/subagents",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/subagents.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/subagents.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/temporal",
+ "destination": "https://steel.dev/docs/cookbook/topics/temporal",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/temporal.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/temporal.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/triggerdev",
+ "destination": "https://steel.dev/docs/cookbook/topics/triggerdev",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/triggerdev.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/triggerdev.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/typed-output",
+ "destination": "https://steel.dev/docs/cookbook/topics/typed-output",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/topics/typed-output.md",
+ "destination": "https://steel.dev/docs/cookbook/topics/typed-output.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/trigger-dev-browser-job",
+ "destination": "https://steel.dev/docs/cookbook/trigger-dev-browser-job",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/trigger-dev-browser-job.md",
+ "destination": "https://steel.dev/docs/cookbook/trigger-dev-browser-job.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/vercel-ai-sdk",
+ "destination": "https://steel.dev/docs/cookbook/vercel-ai-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/vercel-ai-sdk-nextjs",
+ "destination": "https://steel.dev/docs/cookbook/vercel-ai-sdk-nextjs",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/vercel-ai-sdk-nextjs.md",
+ "destination": "https://steel.dev/docs/cookbook/vercel-ai-sdk-nextjs.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/vercel-ai-sdk-ts",
+ "destination": "https://steel.dev/docs/cookbook/vercel-ai-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/vercel-ai-sdk.md",
+ "destination": "https://steel.dev/docs/cookbook/vercel-ai-sdk.md",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/you-com-search",
+ "destination": "https://steel.dev/docs/cookbook/you-com-search",
+ "permanent": true
+ },
+ {
+ "source": "/cookbook/you-com-search.md",
+ "destination": "https://steel.dev/docs/cookbook/you-com-search.md",
+ "permanent": true
+ },
+ {
+ "source": "/favicon.ico",
+ "destination": "https://steel.dev/docs/favicon.ico",
+ "permanent": true
+ },
+ {
+ "source": "/images/1qPnwADZHqUbfaVWwOTmc.png",
+ "destination": "https://steel.dev/docs/images/1qPnwADZHqUbfaVWwOTmc.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/3Ih08HgMtUZuZeoyvEERp.png",
+ "destination": "https://steel.dev/docs/images/3Ih08HgMtUZuZeoyvEERp.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/4-p00Zg3pQ_zsmXrbSfZE.png",
+ "destination": "https://steel.dev/docs/images/4-p00Zg3pQ_zsmXrbSfZE.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/409251245-cc50c603-1cb8-49b1-8910-251b6beec228.mp4",
+ "destination": "https://steel.dev/docs/images/409251245-cc50c603-1cb8-49b1-8910-251b6beec228.mp4",
+ "permanent": true
+ },
+ {
+ "source": "/images/6-qmg3uQon_NBbndvw340.png",
+ "destination": "https://steel.dev/docs/images/6-qmg3uQon_NBbndvw340.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/740kXiKjNNK1O_xf12uE5.png",
+ "destination": "https://steel.dev/docs/images/740kXiKjNNK1O_xf12uE5.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/8LAdzG-x1Li9Nf3Q6VB4P.png",
+ "destination": "https://steel.dev/docs/images/8LAdzG-x1Li9Nf3Q6VB4P.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/AKYFQrDhCB_2-dd2MY_si.png",
+ "destination": "https://steel.dev/docs/images/AKYFQrDhCB_2-dd2MY_si.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/AZtqJmCS-b3Skd0l83dcH.png",
+ "destination": "https://steel.dev/docs/images/AZtqJmCS-b3Skd0l83dcH.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/Bn_vj6cr558fhVAl0PJPX.png",
+ "destination": "https://steel.dev/docs/images/Bn_vj6cr558fhVAl0PJPX.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/ByP4qtaUIR447nJaZqbPS.png",
+ "destination": "https://steel.dev/docs/images/ByP4qtaUIR447nJaZqbPS.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/D-Yt182xdIQAAQph6XjuT.png",
+ "destination": "https://steel.dev/docs/images/D-Yt182xdIQAAQph6XjuT.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/FLSc3KPyPAskPqpEp6oio.png",
+ "destination": "https://steel.dev/docs/images/FLSc3KPyPAskPqpEp6oio.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/LKGCuYzD7oa22Qe7zC0Xv.png",
+ "destination": "https://steel.dev/docs/images/LKGCuYzD7oa22Qe7zC0Xv.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/UgqXSnOj7YuKyieypFDU2.png",
+ "destination": "https://steel.dev/docs/images/UgqXSnOj7YuKyieypFDU2.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/Untitled.png",
+ "destination": "https://steel.dev/docs/images/Untitled.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/a66PwdLRsuZhh62rlIczG.png",
+ "destination": "https://steel.dev/docs/images/a66PwdLRsuZhh62rlIczG.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/aDM_u6zJ-T_yBeJYgkvRh.png",
+ "destination": "https://steel.dev/docs/images/aDM_u6zJ-T_yBeJYgkvRh.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog-placeholder-white.jpeg",
+ "destination": "https://steel.dev/docs/images/changelog-placeholder-white.jpeg",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/22.png",
+ "destination": "https://steel.dev/docs/images/changelog/22.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/23.png",
+ "destination": "https://steel.dev/docs/images/changelog/23.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/24.png",
+ "destination": "https://steel.dev/docs/images/changelog/24.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/25.png",
+ "destination": "https://steel.dev/docs/images/changelog/25.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/26.png",
+ "destination": "https://steel.dev/docs/images/changelog/26.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/27.png",
+ "destination": "https://steel.dev/docs/images/changelog/27.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/28.png",
+ "destination": "https://steel.dev/docs/images/changelog/28.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/29.png",
+ "destination": "https://steel.dev/docs/images/changelog/29.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/30.png",
+ "destination": "https://steel.dev/docs/images/changelog/30.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/31.png",
+ "destination": "https://steel.dev/docs/images/changelog/31.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/32.png",
+ "destination": "https://steel.dev/docs/images/changelog/32.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/33.png",
+ "destination": "https://steel.dev/docs/images/changelog/33.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/34.png",
+ "destination": "https://steel.dev/docs/images/changelog/34.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/35.png",
+ "destination": "https://steel.dev/docs/images/changelog/35.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/36.png",
+ "destination": "https://steel.dev/docs/images/changelog/36.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/37.png",
+ "destination": "https://steel.dev/docs/images/changelog/37.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/changelog/38.png",
+ "destination": "https://steel.dev/docs/images/changelog/38.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/cmbf8zotk0gk8120j8adtmpfu.png",
+ "destination": "https://steel.dev/docs/images/cmbf8zotk0gk8120j8adtmpfu.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/cmbf8zv4x0g1d2b0j2nrm1cwu.png",
+ "destination": "https://steel.dev/docs/images/cmbf8zv4x0g1d2b0j2nrm1cwu.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/cmbf902g80gky2f0ib0zfzqiy.png",
+ "destination": "https://steel.dev/docs/images/cmbf902g80gky2f0ib0zfzqiy.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/cmbf90lm40gslym0i1arf4ivq.png",
+ "destination": "https://steel.dev/docs/images/cmbf90lm40gslym0i1arf4ivq.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/cmbf90t3z0h4k4g0hiqrhnqb2.png",
+ "destination": "https://steel.dev/docs/images/cmbf90t3z0h4k4g0hiqrhnqb2.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/elSgEf1Rrn7oVtsyq2ZGy.png",
+ "destination": "https://steel.dev/docs/images/elSgEf1Rrn7oVtsyq2ZGy.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/etXHsy_awCwDv2MZrMHbX.png",
+ "destination": "https://steel.dev/docs/images/etXHsy_awCwDv2MZrMHbX.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/fYgqRSCjhbqLejUq4fjy8.jpeg",
+ "destination": "https://steel.dev/docs/images/fYgqRSCjhbqLejUq4fjy8.jpeg",
+ "permanent": true
+ },
+ {
+ "source": "/images/jEdp6vzjOSMWbz2ePv3gO.png",
+ "destination": "https://steel.dev/docs/images/jEdp6vzjOSMWbz2ePv3gO.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/lLdC9xMtNw1cihp5shpLU.png",
+ "destination": "https://steel.dev/docs/images/lLdC9xMtNw1cihp5shpLU.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/logo.png",
+ "destination": "https://steel.dev/docs/images/logo.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/oWlREKD5nejyDprfYbiGM.png",
+ "destination": "https://steel.dev/docs/images/oWlREKD5nejyDprfYbiGM.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/rkcB3za0kbZkorDRjHbnV.png",
+ "destination": "https://steel.dev/docs/images/rkcB3za0kbZkorDRjHbnV.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/sYSAX3Mir1aRwCamEtADU.png",
+ "destination": "https://steel.dev/docs/images/sYSAX3Mir1aRwCamEtADU.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/steel-mark.png",
+ "destination": "https://steel.dev/docs/images/steel-mark.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/steel-onboarding.mp4",
+ "destination": "https://steel.dev/docs/images/steel-onboarding.mp4",
+ "permanent": true
+ },
+ {
+ "source": "/images/t61KyxNNN0LQhh_DqvYjp.png",
+ "destination": "https://steel.dev/docs/images/t61KyxNNN0LQhh_DqvYjp.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/x6qu00vtJfpmIZk6aXJZI.png",
+ "destination": "https://steel.dev/docs/images/x6qu00vtJfpmIZk6aXJZI.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/xabZ_F1PSsw-1OaFnvEuE.png",
+ "destination": "https://steel.dev/docs/images/xabZ_F1PSsw-1OaFnvEuE.png",
+ "permanent": true
+ },
+ {
+ "source": "/images/z5OTGZtJklIIs1XG3vPVB.png",
+ "destination": "https://steel.dev/docs/images/z5OTGZtJklIIs1XG3vPVB.png",
+ "permanent": true
+ },
+ {
+ "source": "/integrations",
+ "destination": "https://steel.dev/docs/integrations",
+ "permanent": true
+ },
+ {
+ "source": "/integrations.md",
+ "destination": "https://steel.dev/docs/integrations.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/agentkit",
+ "destination": "https://steel.dev/docs/integrations/agentkit",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/agentkit.md",
+ "destination": "https://steel.dev/docs/integrations/agentkit.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/agno",
+ "destination": "https://steel.dev/docs/integrations/agno",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/agno.md",
+ "destination": "https://steel.dev/docs/integrations/agno.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/ai-sdk",
+ "destination": "https://steel.dev/docs/integrations/ai-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/ai-sdk.md",
+ "destination": "https://steel.dev/docs/integrations/ai-sdk.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/browser-use",
+ "destination": "https://steel.dev/docs/integrations/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/browser-use.md",
+ "destination": "https://steel.dev/docs/integrations/browser-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-agent-sdk",
+ "destination": "https://steel.dev/docs/integrations/claude-agent-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-agent-sdk.md",
+ "destination": "https://steel.dev/docs/integrations/claude-agent-sdk.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-code",
+ "destination": "https://steel.dev/docs/integrations/claude-code",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-code.md",
+ "destination": "https://steel.dev/docs/integrations/claude-code.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-computer-use",
+ "destination": "https://steel.dev/docs/integrations/claude-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/claude-computer-use.md",
+ "destination": "https://steel.dev/docs/integrations/claude-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/codex",
+ "destination": "https://steel.dev/docs/integrations/codex",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/codex.md",
+ "destination": "https://steel.dev/docs/integrations/codex.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/crewai",
+ "destination": "https://steel.dev/docs/integrations/crewai",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/crewai.md",
+ "destination": "https://steel.dev/docs/integrations/crewai.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/gemini-computer-use",
+ "destination": "https://steel.dev/docs/integrations/gemini-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/gemini-computer-use.md",
+ "destination": "https://steel.dev/docs/integrations/gemini-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/hermes-agent",
+ "destination": "https://steel.dev/docs/integrations/hermes-agent",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/hermes-agent.md",
+ "destination": "https://steel.dev/docs/integrations/hermes-agent.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/langgraph",
+ "destination": "https://steel.dev/docs/integrations/langgraph",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/langgraph.md",
+ "destination": "https://steel.dev/docs/integrations/langgraph.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/llms.txt",
+ "destination": "https://steel.dev/docs/integrations/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/magnitude",
+ "destination": "https://steel.dev/docs/integrations/magnitude",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/magnitude.md",
+ "destination": "https://steel.dev/docs/integrations/magnitude.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/mastra",
+ "destination": "https://steel.dev/docs/integrations/mastra",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/mastra.md",
+ "destination": "https://steel.dev/docs/integrations/mastra.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/notte",
+ "destination": "https://steel.dev/docs/integrations/notte",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/notte.md",
+ "destination": "https://steel.dev/docs/integrations/notte.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openai-agents-sdk",
+ "destination": "https://steel.dev/docs/integrations/openai-agents-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openai-agents-sdk.md",
+ "destination": "https://steel.dev/docs/integrations/openai-agents-sdk.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openai-computer-use",
+ "destination": "https://steel.dev/docs/integrations/openai-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openai-computer-use.md",
+ "destination": "https://steel.dev/docs/integrations/openai-computer-use.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openclaw",
+ "destination": "https://steel.dev/docs/integrations/openclaw",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/openclaw.md",
+ "destination": "https://steel.dev/docs/integrations/openclaw.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/pi-agent",
+ "destination": "https://steel.dev/docs/integrations/pi-agent",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/pi-agent.md",
+ "destination": "https://steel.dev/docs/integrations/pi-agent.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/playwright",
+ "destination": "https://steel.dev/docs/integrations/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/playwright.md",
+ "destination": "https://steel.dev/docs/integrations/playwright.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/puppeteer",
+ "destination": "https://steel.dev/docs/integrations/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/puppeteer.md",
+ "destination": "https://steel.dev/docs/integrations/puppeteer.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/pydantic-ai",
+ "destination": "https://steel.dev/docs/integrations/pydantic-ai",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/pydantic-ai.md",
+ "destination": "https://steel.dev/docs/integrations/pydantic-ai.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/replit",
+ "destination": "https://steel.dev/docs/integrations/replit",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/replit.md",
+ "destination": "https://steel.dev/docs/integrations/replit.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/selenium",
+ "destination": "https://steel.dev/docs/integrations/selenium",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/selenium.md",
+ "destination": "https://steel.dev/docs/integrations/selenium.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/stackblitz-bolt.new",
+ "destination": "https://steel.dev/docs/integrations/stackblitz-bolt.new",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/stackblitz-bolt.new.md",
+ "destination": "https://steel.dev/docs/integrations/stackblitz-bolt.new.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/stagehand",
+ "destination": "https://steel.dev/docs/integrations/stagehand",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/stagehand.md",
+ "destination": "https://steel.dev/docs/integrations/stagehand.md",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/valtown/quickstart",
+ "destination": "https://www.val.town/x/steel/steel_puppeteer_starter",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/x402",
+ "destination": "https://steel.dev/docs/integrations/x402",
+ "permanent": true
+ },
+ {
+ "source": "/integrations/x402.md",
+ "destination": "https://steel.dev/docs/integrations/x402.md",
+ "permanent": true
+ },
+ {
+ "source": "/llms-full.txt",
+ "destination": "https://steel.dev/docs/llms-full.txt",
+ "permanent": true
+ },
+ {
+ "source": "/llms.txt",
+ "destination": "https://steel.dev/docs/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-000",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-000",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-001",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-001",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-002",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-002",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-003",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-003",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-004",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-004",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-005",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-005",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-006",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-006",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-007",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-007",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-008",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-008",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-009",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-009",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-010",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-010",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-011",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-011",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-012",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-012",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-013",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-013",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-014",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-014",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-015",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-015",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-016",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-016",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-017",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-017",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-018",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-018",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-019",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-019",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-020",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-020",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-021",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-021",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-022",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-022",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-023",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-023",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-024",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-024",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-025",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-025",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-026",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-026",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-027",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-027",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-028",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-028",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-029",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-029",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-030",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-030",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-031",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-031",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-032",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-032",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-033",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-033",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-034",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-034",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-035",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-035",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-036",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-036",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-037",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-037",
+ "permanent": true
+ },
+ {
+ "source": "/og/changelog/changelog-038",
+ "destination": "https://steel.dev/docs/og/changelog/changelog-038",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook",
+ "destination": "https://steel.dev/docs/og/cookbook",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/agentkit",
+ "destination": "https://steel.dev/docs/og/cookbook/agentkit",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/agno",
+ "destination": "https://steel.dev/docs/og/cookbook/agno",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/auth-context",
+ "destination": "https://steel.dev/docs/og/cookbook/auth-context",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/aspectrr",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/aspectrr",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/bsparker",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/bsparker",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/danew",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/danew",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/hussufo",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/hussufo",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/jagadeshjai",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/jagadeshjai",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/junhsss",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/junhsss",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/authors/nibzard",
+ "destination": "https://steel.dev/docs/og/cookbook/authors/nibzard",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/browser-use",
+ "destination": "https://steel.dev/docs/og/cookbook/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/browser-use-captcha-auto",
+ "destination": "https://steel.dev/docs/og/cookbook/browser-use-captcha-auto",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/browser-use-captcha-manual",
+ "destination": "https://steel.dev/docs/og/cookbook/browser-use-captcha-manual",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/chromedp",
+ "destination": "https://steel.dev/docs/og/cookbook/chromedp",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/chromiumoxide",
+ "destination": "https://steel.dev/docs/og/cookbook/chromiumoxide",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/claude-agent-sdk",
+ "destination": "https://steel.dev/docs/og/cookbook/claude-agent-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/claude-computer-use",
+ "destination": "https://steel.dev/docs/og/cookbook/claude-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/claude-computer-use-mobile",
+ "destination": "https://steel.dev/docs/og/cookbook/claude-computer-use-mobile",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/convex-chat-with-page",
+ "destination": "https://steel.dev/docs/og/cookbook/convex-chat-with-page",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/convex-price-watch",
+ "destination": "https://steel.dev/docs/og/cookbook/convex-price-watch",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/credentials",
+ "destination": "https://steel.dev/docs/og/cookbook/credentials",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/crewai",
+ "destination": "https://steel.dev/docs/og/cookbook/crewai",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/deep-research",
+ "destination": "https://steel.dev/docs/og/cookbook/deep-research",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/eino",
+ "destination": "https://steel.dev/docs/og/cookbook/eino",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/extensions",
+ "destination": "https://steel.dev/docs/og/cookbook/extensions",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/files",
+ "destination": "https://steel.dev/docs/og/cookbook/files",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/gemini-computer-use",
+ "destination": "https://steel.dev/docs/og/cookbook/gemini-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/genkit",
+ "destination": "https://steel.dev/docs/og/cookbook/genkit",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/google-adk",
+ "destination": "https://steel.dev/docs/og/cookbook/google-adk",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/headless-chrome",
+ "destination": "https://steel.dev/docs/og/cookbook/headless-chrome",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/langchaingo",
+ "destination": "https://steel.dev/docs/og/cookbook/langchaingo",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/langgraph",
+ "destination": "https://steel.dev/docs/og/cookbook/langgraph",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/magnitude",
+ "destination": "https://steel.dev/docs/og/cookbook/magnitude",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/mastra",
+ "destination": "https://steel.dev/docs/og/cookbook/mastra",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/mcp",
+ "destination": "https://steel.dev/docs/og/cookbook/mcp",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/microsoft-agent-framework",
+ "destination": "https://steel.dev/docs/og/cookbook/microsoft-agent-framework",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/notte",
+ "destination": "https://steel.dev/docs/og/cookbook/notte",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/openai-agents",
+ "destination": "https://steel.dev/docs/og/cookbook/openai-agents",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/openai-computer-use",
+ "destination": "https://steel.dev/docs/og/cookbook/openai-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/playwright",
+ "destination": "https://steel.dev/docs/og/cookbook/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/profiles",
+ "destination": "https://steel.dev/docs/og/cookbook/profiles",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/puppeteer",
+ "destination": "https://steel.dev/docs/og/cookbook/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/pydantic-ai",
+ "destination": "https://steel.dev/docs/og/cookbook/pydantic-ai",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/restate-agent",
+ "destination": "https://steel.dev/docs/og/cookbook/restate-agent",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/rig",
+ "destination": "https://steel.dev/docs/og/cookbook/rig",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/rod",
+ "destination": "https://steel.dev/docs/og/cookbook/rod",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/scrape",
+ "destination": "https://steel.dev/docs/og/cookbook/scrape",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/selenium",
+ "destination": "https://steel.dev/docs/og/cookbook/selenium",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/stagehand",
+ "destination": "https://steel.dev/docs/og/cookbook/stagehand",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/swiftide",
+ "destination": "https://steel.dev/docs/og/cookbook/swiftide",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/temporal-browser-workflow",
+ "destination": "https://steel.dev/docs/og/cookbook/temporal-browser-workflow",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/agents",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/agents",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/authentication",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/authentication",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/browser-automation",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/browser-automation",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/browser-use",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/captchas",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/captchas",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/computer-use",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/convex",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/convex",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/mcp",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/mcp",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/mobile",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/mobile",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/nextjs",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/nextjs",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/playwright",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/restate",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/restate",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/search",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/search",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/steel-apis",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/steel-apis",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/subagents",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/subagents",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/temporal",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/temporal",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/triggerdev",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/triggerdev",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/topics/typed-output",
+ "destination": "https://steel.dev/docs/og/cookbook/topics/typed-output",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/trigger-dev-browser-job",
+ "destination": "https://steel.dev/docs/og/cookbook/trigger-dev-browser-job",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/vercel-ai-sdk",
+ "destination": "https://steel.dev/docs/og/cookbook/vercel-ai-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/vercel-ai-sdk-nextjs",
+ "destination": "https://steel.dev/docs/og/cookbook/vercel-ai-sdk-nextjs",
+ "permanent": true
+ },
+ {
+ "source": "/og/cookbook/you-com-search",
+ "destination": "https://steel.dev/docs/og/cookbook/you-com-search",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations",
+ "destination": "https://steel.dev/docs/og/integrations",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/agentkit",
+ "destination": "https://steel.dev/docs/og/integrations/agentkit",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/agno",
+ "destination": "https://steel.dev/docs/og/integrations/agno",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/ai-sdk",
+ "destination": "https://steel.dev/docs/og/integrations/ai-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/browser-use",
+ "destination": "https://steel.dev/docs/og/integrations/browser-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/claude-agent-sdk",
+ "destination": "https://steel.dev/docs/og/integrations/claude-agent-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/claude-code",
+ "destination": "https://steel.dev/docs/og/integrations/claude-code",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/claude-computer-use",
+ "destination": "https://steel.dev/docs/og/integrations/claude-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/codex",
+ "destination": "https://steel.dev/docs/og/integrations/codex",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/crewai",
+ "destination": "https://steel.dev/docs/og/integrations/crewai",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/gemini-computer-use",
+ "destination": "https://steel.dev/docs/og/integrations/gemini-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/hermes-agent",
+ "destination": "https://steel.dev/docs/og/integrations/hermes-agent",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/langgraph",
+ "destination": "https://steel.dev/docs/og/integrations/langgraph",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/magnitude",
+ "destination": "https://steel.dev/docs/og/integrations/magnitude",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/mastra",
+ "destination": "https://steel.dev/docs/og/integrations/mastra",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/notte",
+ "destination": "https://steel.dev/docs/og/integrations/notte",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/openai-agents-sdk",
+ "destination": "https://steel.dev/docs/og/integrations/openai-agents-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/openai-computer-use",
+ "destination": "https://steel.dev/docs/og/integrations/openai-computer-use",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/openclaw",
+ "destination": "https://steel.dev/docs/og/integrations/openclaw",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/pi-agent",
+ "destination": "https://steel.dev/docs/og/integrations/pi-agent",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/playwright",
+ "destination": "https://steel.dev/docs/og/integrations/playwright",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/puppeteer",
+ "destination": "https://steel.dev/docs/og/integrations/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/pydantic-ai",
+ "destination": "https://steel.dev/docs/og/integrations/pydantic-ai",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/replit",
+ "destination": "https://steel.dev/docs/og/integrations/replit",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/selenium",
+ "destination": "https://steel.dev/docs/og/integrations/selenium",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/stackblitz-bolt.new",
+ "destination": "https://steel.dev/docs/og/integrations/stackblitz-bolt.new",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/stagehand",
+ "destination": "https://steel.dev/docs/og/integrations/stagehand",
+ "permanent": true
+ },
+ {
+ "source": "/og/integrations/x402",
+ "destination": "https://steel.dev/docs/og/integrations/x402",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/agent-traces/api",
+ "destination": "https://steel.dev/docs/og/overview/agent-traces/api",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/agent-traces/overview",
+ "destination": "https://steel.dev/docs/og/overview/agent-traces/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/agent-traces/timeline",
+ "destination": "https://steel.dev/docs/og/overview/agent-traces/timeline",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/authentication",
+ "destination": "https://steel.dev/docs/og/overview/authentication",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/browser-tools/overview",
+ "destination": "https://steel.dev/docs/og/overview/browser-tools/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/captchas-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/captchas-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/credentials-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/credentials-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/extensions-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/extensions-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/files-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/files-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/intro-to-steel",
+ "destination": "https://steel.dev/docs/og/overview/intro-to-steel",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/legal",
+ "destination": "https://steel.dev/docs/og/overview/legal",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/llms-full.txt",
+ "destination": "https://steel.dev/docs/og/overview/llms-full.txt",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/need-help",
+ "destination": "https://steel.dev/docs/og/overview/need-help",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/pricinglimits",
+ "destination": "https://steel.dev/docs/og/overview/pricinglimits",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/profiles-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/profiles-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/clustering",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/clustering",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/docker",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/docker",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/extensions",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/extensions",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/profiles",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/profiles",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/railway",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/railway",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/render",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/render",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/steel-local-vs-steel-cloud",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/steel-local-vs-steel-cloud",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/self-hosting/webrtc",
+ "destination": "https://steel.dev/docs/og/overview/self-hosting/webrtc",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/configuration",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/configuration",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/dedicated-ips",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/dedicated-ips",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/embed-sessions",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/embed-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/embed-sessions/live-sessions",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/embed-sessions/live-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/embed-sessions/past-sessions",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/embed-sessions/past-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/fullscreen-mode",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/fullscreen-mode",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/human-in-the-loop",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/human-in-the-loop",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/mobile-mode",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/mobile-mode",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/overview",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/quickstart",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/quickstart",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/reusing-auth-context",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/reusing-auth-context",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/sessions-api/session-lifecycle",
+ "destination": "https://steel.dev/docs/og/overview/sessions-api/session-lifecycle",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills",
+ "destination": "https://steel.dev/docs/og/overview/skills",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills/steel-browser",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills/steel-browser",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills/steel-developer",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills/steel-developer",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills/steel-reliability",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills/steel-reliability",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills/steel-session-debugging",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills/steel-session-debugging",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/available-skills/steel-skill-creator",
+ "destination": "https://steel.dev/docs/og/overview/skills/available-skills/steel-skill-creator",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/skills/troubleshooting",
+ "destination": "https://steel.dev/docs/og/overview/skills/troubleshooting",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/stealth/captcha-solving",
+ "destination": "https://steel.dev/docs/og/overview/stealth/captcha-solving",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/stealth/proxies",
+ "destination": "https://steel.dev/docs/og/overview/stealth/proxies",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/steel-cli",
+ "destination": "https://steel.dev/docs/og/overview/steel-cli",
+ "permanent": true
+ },
+ {
+ "source": "/og/overview/stripe-projects",
+ "destination": "https://steel.dev/docs/og/overview/stripe-projects",
+ "permanent": true
+ },
+ {
+ "source": "/openapi.json",
+ "destination": "https://api.steel.dev/sdk-openapi.json",
+ "permanent": true
+ },
+ {
+ "source": "/overview",
+ "destination": "https://steel.dev/docs",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/api",
+ "destination": "https://steel.dev/docs/overview/agent-traces/api",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/api.md",
+ "destination": "https://steel.dev/docs/overview/agent-traces/api.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/llms.txt",
+ "destination": "https://steel.dev/docs/overview/agent-traces/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/overview",
+ "destination": "https://steel.dev/docs/overview/agent-traces/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/overview.md",
+ "destination": "https://steel.dev/docs/overview/agent-traces/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/timeline",
+ "destination": "https://steel.dev/docs/overview/agent-traces/timeline",
+ "permanent": true
+ },
+ {
+ "source": "/overview/agent-traces/timeline.md",
+ "destination": "https://steel.dev/docs/overview/agent-traces/timeline.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/authentication",
+ "destination": "https://steel.dev/docs/overview/authentication",
+ "permanent": true
+ },
+ {
+ "source": "/overview/authentication.md",
+ "destination": "https://steel.dev/docs/overview/authentication.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/browser-tools/llms.txt",
+ "destination": "https://steel.dev/docs/overview/browser-tools/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/browser-tools/overview",
+ "destination": "https://steel.dev/docs/overview/browser-tools/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/browser-tools/overview.md",
+ "destination": "https://steel.dev/docs/overview/browser-tools/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/captchas-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/captchas-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/captchas-api/overview",
+ "destination": "https://steel.dev/docs/overview/captchas-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/captchas-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/captchas-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/credentials-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/credentials-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/credentials-api/overview",
+ "destination": "https://steel.dev/docs/overview/credentials-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/credentials-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/credentials-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/extensions-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/extensions-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/extensions-api/overview",
+ "destination": "https://steel.dev/docs/overview/extensions-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/extensions-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/extensions-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/files-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/files-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/files-api/overview",
+ "destination": "https://steel.dev/docs/overview/files-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/files-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/files-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/guides/playwright-node",
+ "destination": "https://steel.dev/docs/cookbook/playwright#typescript",
+ "permanent": true
+ },
+ {
+ "source": "/overview/guides/playwright-python",
+ "destination": "https://steel.dev/docs/cookbook/playwright#python",
+ "permanent": true
+ },
+ {
+ "source": "/overview/guides/puppeteer",
+ "destination": "https://steel.dev/docs/cookbook/puppeteer",
+ "permanent": true
+ },
+ {
+ "source": "/overview/guides/selenium",
+ "destination": "https://steel.dev/docs/cookbook/selenium",
+ "permanent": true
+ },
+ {
+ "source": "/overview/intro-to-steel",
+ "destination": "https://steel.dev/docs/overview/intro-to-steel",
+ "permanent": true
+ },
+ {
+ "source": "/overview/intro-to-steel.md",
+ "destination": "https://steel.dev/docs/overview/intro-to-steel.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/legal",
+ "destination": "https://steel.dev/docs/overview/legal",
+ "permanent": true
+ },
+ {
+ "source": "/overview/legal.md",
+ "destination": "https://steel.dev/docs/overview/legal.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/llms-full.txt",
+ "destination": "https://steel.dev/docs/llms-full.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/llms.txt",
+ "destination": "https://steel.dev/docs/overview/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/need-help",
+ "destination": "https://steel.dev/docs/overview/need-help",
+ "permanent": true
+ },
+ {
+ "source": "/overview/need-help.md",
+ "destination": "https://steel.dev/docs/overview/need-help.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/pricinglimits",
+ "destination": "https://steel.dev/docs/overview/pricinglimits",
+ "permanent": true
+ },
+ {
+ "source": "/overview/pricinglimits.md",
+ "destination": "https://steel.dev/docs/overview/pricinglimits.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/profiles-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/profiles-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/profiles-api/overview",
+ "destination": "https://steel.dev/docs/overview/profiles-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/profiles-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/profiles-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/clustering",
+ "destination": "https://steel.dev/docs/overview/self-hosting/clustering",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/clustering.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/clustering.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/docker",
+ "destination": "https://steel.dev/docs/overview/self-hosting/docker",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/docker.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/docker.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/extensions",
+ "destination": "https://steel.dev/docs/overview/self-hosting/extensions",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/extensions.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/extensions.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/llms.txt",
+ "destination": "https://steel.dev/docs/overview/self-hosting/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/profiles",
+ "destination": "https://steel.dev/docs/overview/self-hosting/profiles",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/profiles.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/profiles.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/railway",
+ "destination": "https://steel.dev/docs/overview/self-hosting/railway",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/railway.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/railway.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/render",
+ "destination": "https://render.com/deploy?image=steeldev/steel",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/steel-local-vs-steel-cloud",
+ "destination": "https://steel.dev/docs/overview/self-hosting/steel-local-vs-steel-cloud",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/steel-local-vs-steel-cloud.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/steel-local-vs-steel-cloud.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/webrtc",
+ "destination": "https://steel.dev/docs/overview/self-hosting/webrtc",
+ "permanent": true
+ },
+ {
+ "source": "/overview/self-hosting/webrtc.md",
+ "destination": "https://steel.dev/docs/overview/self-hosting/webrtc.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/configuration",
+ "destination": "https://steel.dev/docs/overview/sessions-api/configuration",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/configuration.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/configuration.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/dedicated-ips",
+ "destination": "https://steel.dev/docs/overview/sessions-api/dedicated-ips",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/dedicated-ips.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/dedicated-ips.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions/live-sessions",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions/live-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions/live-sessions.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions/live-sessions.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions/llms.txt",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions/past-sessions",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions/past-sessions",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/embed-sessions/past-sessions.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/embed-sessions/past-sessions.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/fullscreen-mode",
+ "destination": "https://steel.dev/docs/overview/sessions-api/fullscreen-mode",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/fullscreen-mode.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/fullscreen-mode.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/human-in-the-loop",
+ "destination": "https://steel.dev/docs/overview/sessions-api/human-in-the-loop",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/human-in-the-loop.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/human-in-the-loop.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/llms.txt",
+ "destination": "https://steel.dev/docs/overview/sessions-api/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/mobile-mode",
+ "destination": "https://steel.dev/docs/overview/sessions-api/mobile-mode",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/mobile-mode.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/mobile-mode.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/multi-region",
+ "destination": "https://steel.dev/docs/overview/sessions-api/configuration#network-identity",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/overview",
+ "destination": "https://steel.dev/docs/overview/sessions-api/overview",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/overview.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/overview.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/quickstart",
+ "destination": "https://steel.dev/docs/overview/sessions-api/quickstart",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/quickstart.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/quickstart.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/reusing-auth-context",
+ "destination": "https://steel.dev/docs/overview/sessions-api/reusing-auth-context",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/reusing-auth-context.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/reusing-auth-context.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/session-lifecycle",
+ "destination": "https://steel.dev/docs/overview/sessions-api/session-lifecycle",
+ "permanent": true
+ },
+ {
+ "source": "/overview/sessions-api/session-lifecycle.md",
+ "destination": "https://steel.dev/docs/overview/sessions-api/session-lifecycle.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills",
+ "destination": "https://steel.dev/docs/overview/skills",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills.md",
+ "destination": "https://steel.dev/docs/overview/skills.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/llms.txt",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-browser",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-browser",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-browser.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-browser.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-developer",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-developer",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-developer.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-developer.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-reliability",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-reliability",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-reliability.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-reliability.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-session-debugging",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-session-debugging",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-session-debugging.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-session-debugging.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-skill-creator",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-skill-creator",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/available-skills/steel-skill-creator.md",
+ "destination": "https://steel.dev/docs/overview/skills/available-skills/steel-skill-creator.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/llms.txt",
+ "destination": "https://steel.dev/docs/overview/skills/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/troubleshooting",
+ "destination": "https://steel.dev/docs/overview/skills/troubleshooting",
+ "permanent": true
+ },
+ {
+ "source": "/overview/skills/troubleshooting.md",
+ "destination": "https://steel.dev/docs/overview/skills/troubleshooting.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stealth/captcha-solving",
+ "destination": "https://steel.dev/docs/overview/stealth/captcha-solving",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stealth/captcha-solving.md",
+ "destination": "https://steel.dev/docs/overview/stealth/captcha-solving.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stealth/llms.txt",
+ "destination": "https://steel.dev/docs/overview/stealth/llms.txt",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stealth/proxies",
+ "destination": "https://steel.dev/docs/overview/stealth/proxies",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stealth/proxies.md",
+ "destination": "https://steel.dev/docs/overview/stealth/proxies.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/steel-cli",
+ "destination": "https://steel.dev/docs/overview/steel-cli",
+ "permanent": true
+ },
+ {
+ "source": "/overview/steel-cli.md",
+ "destination": "https://steel.dev/docs/overview/steel-cli.md",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stripe-projects",
+ "destination": "https://steel.dev/docs/overview/stripe-projects",
+ "permanent": true
+ },
+ {
+ "source": "/overview/stripe-projects.md",
+ "destination": "https://steel.dev/docs/overview/stripe-projects.md",
+ "permanent": true
+ },
+ {
+ "source": "/start",
+ "destination": "https://steel.dev/docs",
+ "permanent": false
+ },
+ {
+ "source": "/steel-go-sdk",
+ "destination": "https://pkg.go.dev/github.com/steel-dev/steel-go",
+ "permanent": true
+ },
+ {
+ "source": "/steel-js-sdk",
+ "destination": "https://www.npmjs.com/package/steel-sdk",
+ "permanent": true
+ },
+ {
+ "source": "/steel-python-sdk",
+ "destination": "https://pypi.org/project/steel-sdk/",
+ "permanent": true
+ },
+ {
+ "source": "/steel-rust-sdk",
+ "destination": "https://crates.io/crates/steel-rs",
+ "permanent": true
+ }
+ ]
+}
diff --git a/hooks/use-llms-txt.ts b/hooks/use-llms-txt.ts
index b7fedd0ba..f18e95f40 100644
--- a/hooks/use-llms-txt.ts
+++ b/hooks/use-llms-txt.ts
@@ -1,39 +1,37 @@
-import { useQuery } from "@tanstack/react-query";
-import { usePathname } from "next/navigation";
+import { useQuery } from '@tanstack/react-query';
+import { usePathname } from 'next/navigation';
+import { DOCS_PATH_PREFIX, docsPath, stripDocsPath } from '@/lib/docs-path';
export function useLLMsTxt() {
const pathname = usePathname();
// Calculate the static file path based on current pathname
const getStaticPath = () => {
- if (!pathname || pathname === "/") return "/llms.txt";
+ if (!pathname || stripDocsPath(pathname) === '/') return docsPath('/llms.txt');
// Remove /docs prefix if present
- let path = pathname;
- if (path.startsWith("/docs")) {
- path = path.substring(5);
- }
+ const path = stripDocsPath(pathname);
// For specific pages, use the parent directory's llms.txt
- const segments = path.split("/").filter(Boolean);
+ const segments = path.split('/').filter(Boolean);
if (segments.length > 0) {
// Remove the last segment (the page name) to get the section
- const sectionPath = segments.slice(0, -1).join("/");
- return sectionPath ? `/${sectionPath}/llms.txt` : "/llms.txt";
+ const sectionPath = segments.slice(0, -1).join('/');
+ return docsPath(sectionPath ? `/${sectionPath}/llms.txt` : '/llms.txt');
}
- return "/llms.txt";
+ return docsPath('/llms.txt');
};
const staticPath = getStaticPath();
return useQuery({
- queryKey: ["llms-txt", staticPath],
+ queryKey: ['llms-txt', staticPath],
queryFn: async () => {
const response = await fetch(staticPath);
if (!response.ok) {
// Fallback to root llms.txt if section-specific doesn't exist
- const rootResponse = await fetch("/llms.txt");
+ const rootResponse = await fetch(docsPath('/llms.txt'));
if (!rootResponse.ok) {
throw new Error(`Failed to fetch LLM context`);
}
@@ -51,23 +49,23 @@ export function useLLMsTxt() {
// (e.g. /overview/intro.md). The docs root has no markdown page, so it points
// at the /llms.txt index instead. Pure so the convention is unit-testable.
export function getPublicMarkdownUrl(pathname: string | null, origin: string) {
- if (!pathname) return "";
+ if (!pathname) return '';
// Remove /docs prefix if present
- let basePath = pathname;
- if (basePath.startsWith("/docs")) {
- basePath = basePath.substring(5);
- }
+ const basePath =
+ !DOCS_PATH_PREFIX && (pathname === '/docs' || pathname.startsWith('/docs/'))
+ ? pathname.slice(5) || '/'
+ : stripDocsPath(pathname);
- const mdPath = basePath.startsWith("/") ? basePath : "/" + basePath;
+ const mdPath = basePath.startsWith('/') ? basePath : '/' + basePath;
- return mdPath === "/" ? `${origin}/llms.txt` : `${origin}${mdPath}.md`;
+ return `${origin}${docsPath(mdPath === '/' ? '/llms.txt' : `${mdPath}.md`)}`;
}
// Hook for getting the current page's markdown URL
export function useCurrentPageMarkdown() {
const pathname = usePathname();
- const origin = typeof window !== "undefined" ? window.location.origin : "";
+ const origin = typeof window !== 'undefined' ? window.location.origin : '';
return getPublicMarkdownUrl(pathname, origin);
}
diff --git a/lib/agent-instructions.ts b/lib/agent-instructions.ts
index 2e591501c..23c16fe95 100644
--- a/lib/agent-instructions.ts
+++ b/lib/agent-instructions.ts
@@ -1,6 +1,8 @@
// ABOUTME: Quick reference and gotchas served to agents via the /llms.txt index,
// ABOUTME: the /llms-full.txt bundle, and the /AGENTS.md route.
+import { docsPath, docsUrl } from './docs-path';
+
export const AGENT_INSTRUCTIONS = `# Steel Documentation
> Steel is the open-source browser API for AI agents — managed cloud browsers with stealth,
@@ -16,7 +18,7 @@ export const AGENT_INSTRUCTIONS = `# Steel Documentation
- API base URL: \`https://api.steel.dev\`
- WebSocket: \`wss://connect.steel.dev?apiKey=&sessionId=\`
- API reference: https://steel.apidocumentation.com/api-reference
-- Brand design language (DESIGN.md): https://docs.steel.dev/DESIGN.md
+- Brand design language (DESIGN.md): ${docsUrl('/DESIGN.md')}
## Agent Instructions
@@ -67,7 +69,7 @@ export const AGENT_INSTRUCTIONS = `# Steel Documentation
- The Python package installs as \`pip install steel-sdk\` but imports as \`from steel import Steel\`
- \`sessions.create()\` accepts an optional \`sessionId\` (Node) / \`session_id\` (Python) UUID when you need the ID before the session exists; omit it and Steel generates one
- The Python \`sessions.create()\` session timeout param is \`api_timeout\` (not \`timeout\`, which is the HTTP request timeout)
-- Any docs page is available as markdown by appending \`.md\` to its URL, for example \`https://docs.steel.dev/overview/steel-cli.md\`; AI user agents (Claude, Cursor, GPT) receive markdown automatically at the canonical URL
-- \`/llms-full.txt\` concatenates every page into one file (large, roughly 230k tokens); prefer fetching the individual \`.md\` pages you need over reading the whole bundle
+- Any docs page is available as markdown by appending \`.md\` to its URL, for example \`${docsUrl('/overview/steel-cli.md')}\`; AI user agents (Claude, Cursor, GPT) receive markdown automatically at the canonical URL
+- \`${docsPath('/llms-full.txt')}\` concatenates every page into one file (large, roughly 230k tokens); prefer fetching the individual \`.md\` pages you need over reading the whole bundle
`;
diff --git a/lib/docs-host-redirects.ts b/lib/docs-host-redirects.ts
new file mode 100644
index 000000000..76038de1e
--- /dev/null
+++ b/lib/docs-host-redirects.ts
@@ -0,0 +1,165 @@
+// ABOUTME: Builds the one-to-one redirect map from docs.steel.dev URLs to https://steel.dev/docs.
+// ABOUTME: The docs-host-redirect Vercel project serves this map after the docs move to steel.dev.
+
+/** The public docs home after the move. Every internal destination starts with it. */
+export const NEW_DOCS_URL = 'https://steel.dev/docs';
+/** The docs host before the move. */
+export const OLD_DOCS_ORIGIN = 'https://docs.steel.dev';
+/** Vercel accepts at most this number of redirects in one vercel.json. */
+export const MAX_VERCEL_REDIRECTS = 2048;
+
+export interface Redirect {
+ source: string;
+ destination: string;
+ permanent: boolean;
+}
+
+export interface HostRedirectInput {
+ /** Root-mode page URLs from the Fumadocs source, for example `/overview/steel-cli`. */
+ pages: string[];
+ /** The pages that also serve Markdown at `.md` (the ones without `llm: false`). */
+ markdownPages: string[];
+ /** Root-mode paths of files in `public/`, for example `/images/logo.png`. */
+ publicFiles: string[];
+ /** Root-mode paths that app routes serve, for example `/llms.txt`. */
+ routes: string[];
+ /** Root-mode redirects that the docs app has, from next.config.mjs and app pages. */
+ legacy: Redirect[];
+}
+
+/** Paths that the redirect project serves itself, so that crawlers can read the old host. */
+export const SERVED_BY_REDIRECT_PROJECT = ['/robots.txt', '/sitemap.xml'];
+
+/** Gives the URL on steel.dev for a root-mode docs path. Fragments and queries stay. */
+export function newDocsUrl(path: string): string {
+ if (!path.startsWith('/') || path.startsWith('//')) {
+ throw new Error(`Not a root-relative docs path: ${path}`);
+ }
+ if (path === '/') return NEW_DOCS_URL;
+ if (path.startsWith('/#') || path.startsWith('/?')) return `${NEW_DOCS_URL}${path.slice(1)}`;
+ return `${NEW_DOCS_URL}${path}`;
+}
+
+/**
+ * Escapes a literal path for a Vercel redirect `source`. Vercel reads `source` as a
+ * path-to-regexp pattern, so these characters would otherwise start a parameter or group.
+ */
+export function escapeSource(path: string): string {
+ return path.replace(/[:()*+?{}\\]/g, (character) => `\\${character}`);
+}
+
+function pathOnly(url: string): string {
+ return url.replace(/[?#].*$/, '');
+}
+
+/** Follows internal redirect chains so that each old URL needs one hop only. */
+function resolveLegacy(legacy: Redirect[]): Map {
+ const bySource = new Map(legacy.map((redirect) => [redirect.source, redirect]));
+ const resolved = new Map();
+ for (const redirect of legacy) {
+ let destination = redirect.destination;
+ let permanent = redirect.permanent;
+ const seen = new Set([redirect.source]);
+ while (destination.startsWith('/')) {
+ const next = bySource.get(pathOnly(destination));
+ if (!next) break;
+ if (seen.has(next.source)) throw new Error(`Redirect loop at ${redirect.source}`);
+ seen.add(next.source);
+ destination = next.destination;
+ permanent = permanent && next.permanent;
+ }
+ resolved.set(redirect.source, {
+ source: redirect.source,
+ destination: destination.startsWith('/') ? newDocsUrl(destination) : destination,
+ permanent,
+ });
+ }
+ return resolved;
+}
+
+/**
+ * Builds the complete map. Each page gets its HTML URL and its Open Graph image URL, and
+ * each Markdown page also gets its `.md` URL. Each public file and app route keeps its path under `/docs`. Old redirects go
+ * straight to their final destination, and they win over a page at the same path, as the
+ * redirects in next.config.mjs do. The output is sorted by source.
+ */
+export function buildHostRedirects(input: HostRedirectInput): Redirect[] {
+ const map = new Map();
+ for (const redirect of resolveLegacy(input.legacy).values()) {
+ map.set(redirect.source, redirect);
+ }
+ const keep = (path: string) => {
+ if (!map.has(path))
+ map.set(path, { source: path, destination: newDocsUrl(path), permanent: true });
+ };
+
+ keep('/');
+ for (const page of input.pages) {
+ if (page === '/') continue;
+ keep(page);
+ keep(`/og${page}`);
+ }
+ for (const page of input.markdownPages) {
+ // A page that an old redirect hides has no Markdown URL, but it keeps its image.
+ if (page !== '/' && !input.legacy.some((redirect) => redirect.source === page)) {
+ keep(`${page}.md`);
+ }
+ }
+ for (const file of input.publicFiles) {
+ if (!SERVED_BY_REDIRECT_PROJECT.includes(file)) keep(file);
+ }
+ for (const route of input.routes) {
+ if (!SERVED_BY_REDIRECT_PROJECT.includes(route)) keep(route);
+ }
+
+ const redirects = [...map.values()].sort((a, b) =>
+ a.source < b.source ? -1 : a.source > b.source ? 1 : 0,
+ );
+ if (redirects.length > MAX_VERCEL_REDIRECTS) {
+ throw new Error(
+ `${redirects.length} redirects exceed the Vercel limit of ${MAX_VERCEL_REDIRECTS}`,
+ );
+ }
+ return redirects;
+}
+
+/** Gives the vercel.json of the redirect project. */
+export function renderVercelJson(redirects: Redirect[]): string {
+ const config = {
+ $schema: 'https://openapi.vercel.sh/vercel.json',
+ framework: null,
+ installCommand: '',
+ buildCommand: '',
+ outputDirectory: 'public',
+ trailingSlash: false,
+ // Build only when this directory changes; other docs commits do not change the map.
+ ignoreCommand: 'git diff --quiet HEAD^ HEAD -- .',
+ redirects: redirects.map((redirect) => ({
+ source: escapeSource(redirect.source),
+ destination: redirect.destination,
+ permanent: redirect.permanent,
+ })),
+ };
+ return `${JSON.stringify(config, null, 2)}\n`;
+}
+
+/** Gives the old-host sitemap: every old page URL, sorted, so that crawlers find each redirect. */
+export function renderOldSitemap(pages: string[]): string {
+ const urls = ['/', ...pages.filter((page) => page !== '/').sort()]
+ .map((page) => `${OLD_DOCS_ORIGIN}${page === '/' ? '/' : page} `)
+ .join('\n');
+ return `\n\n${urls}\n \n`;
+}
+
+/** Gives the old-host robots.txt. Crawlers must be able to fetch old URLs to see the redirects. */
+export function renderOldRobots(): string {
+ return [
+ '# docs.steel.dev moved to https://steel.dev/docs. Each old URL redirects there.',
+ 'User-agent: *',
+ 'Allow: /',
+ '',
+ `Sitemap: ${OLD_DOCS_ORIGIN}/sitemap.xml`,
+ `Sitemap: ${NEW_DOCS_URL}/sitemap.xml`,
+ '',
+ ].join('\n');
+}
diff --git a/lib/docs-path.ts b/lib/docs-path.ts
new file mode 100644
index 000000000..cf1d41852
--- /dev/null
+++ b/lib/docs-path.ts
@@ -0,0 +1,39 @@
+// ABOUTME: Holds the public docs URL boundary: the path prefix (empty or /docs) and the public origin.
+// ABOUTME: docsPath, stripDocsPath and docsUrl are the only place that knows how docs URLs look.
+/** Public URL boundary; an empty prefix preserves the existing subdomain build. */
+export const DOCS_PATH_PREFIX = process.env.NEXT_PUBLIC_DOCS_PATH_PREFIX || '';
+if (DOCS_PATH_PREFIX !== '' && DOCS_PATH_PREFIX !== '/docs') {
+ throw new Error('NEXT_PUBLIC_DOCS_PATH_PREFIX must be empty or /docs');
+}
+export const DOCS_ORIGIN = process.env.NEXT_PUBLIC_DOCS_ORIGIN || 'https://docs.steel.dev';
+if (new URL(DOCS_ORIGIN).origin !== DOCS_ORIGIN) {
+ throw new Error('NEXT_PUBLIC_DOCS_ORIGIN must be an origin without a path');
+}
+
+export function docsPath(path: string): string {
+ if (DOCS_PATH_PREFIX && /^https:\/\/docs\.steel\.dev(?:[/?#]|$)/.test(path)) {
+ const url = new URL(path);
+ path = `${url.pathname}${url.search}${url.hash}`;
+ }
+ if (!path.startsWith('/') || path.startsWith('//')) return path;
+ if (
+ !DOCS_PATH_PREFIX ||
+ path === DOCS_PATH_PREFIX ||
+ path.startsWith(`${DOCS_PATH_PREFIX}/`) ||
+ path.startsWith(`${DOCS_PATH_PREFIX}?`) ||
+ path.startsWith(`${DOCS_PATH_PREFIX}#`)
+ )
+ return path;
+ return `${DOCS_PATH_PREFIX}${path === '/' ? '' : path}`;
+}
+
+export function stripDocsPath(path: string): string {
+ if (DOCS_PATH_PREFIX && path === DOCS_PATH_PREFIX) return '/';
+ return DOCS_PATH_PREFIX && path.startsWith(`${DOCS_PATH_PREFIX}/`)
+ ? path.slice(DOCS_PATH_PREFIX.length)
+ : path;
+}
+
+export function docsUrl(path = '/'): string {
+ return new URL(docsPath(path), DOCS_ORIGIN).href;
+}
diff --git a/lib/get-llm-text.ts b/lib/get-llm-text.ts
index 278aaa560..1d9e128c5 100644
--- a/lib/get-llm-text.ts
+++ b/lib/get-llm-text.ts
@@ -1,6 +1,7 @@
import type { InferPageType } from 'fumadocs-core/source';
import matter from 'gray-matter';
-import { cleanMdxForLLM } from '@/lib/llm-markdown';
+import { DOCS_ORIGIN, DOCS_PATH_PREFIX, docsPath, stripDocsPath } from '@/lib/docs-path';
+import { cleanMdxForLLM, mapMarkdownLinks } from '@/lib/llm-markdown';
import { source } from '@/lib/source';
export function shouldIncludeLLMPage(page: InferPageType) {
@@ -16,7 +17,10 @@ export function shouldIncludeLLMPage(page: InferPageType) {
}
}
-const SITE_URL = process.env.LLMS_BASE_URL || 'https://docs.steel.dev';
+const SITE_URL = (process.env.LLMS_BASE_URL || `${DOCS_ORIGIN}${DOCS_PATH_PREFIX}`).replace(
+ /\/$/,
+ '',
+);
export const LLMS_INDEX_POINTER = `> Full docs index: ${SITE_URL}/llms.txt`;
@@ -24,11 +28,11 @@ export async function getLLMText(
page: InferPageType,
options: { indexPointer?: boolean } = {},
) {
- const processed = cleanMdxForLLM(page.data.content);
+ const processed = mapMarkdownLinks(cleanMdxForLLM(page.data.content), docsPath);
const pointer = options.indexPointer ? `${LLMS_INDEX_POINTER}\n\n` : '';
return `${pointer}# ${page.data.title}
-URL: ${SITE_URL}${page.url}
+URL: ${SITE_URL}${stripDocsPath(page.url)}
${processed}`;
}
diff --git a/lib/llm-markdown.ts b/lib/llm-markdown.ts
index 4292e04df..ed54420dd 100644
--- a/lib/llm-markdown.ts
+++ b/lib/llm-markdown.ts
@@ -409,3 +409,35 @@ export function cleanMdxForLLM(content: string): string {
),
);
}
+
+/** Adjust link destinations without modifying fenced or inline code examples. */
+export function mapMarkdownLinks(content: string, mapUrl: (url: string) => string): string {
+ let fence: Fence | null = null;
+ return content
+ .split('\n')
+ .map((line) => {
+ if (fence) {
+ if (closesFence(line, fence)) fence = null;
+ return line;
+ }
+ const parsed = parseFenceLine(line);
+ if (parsed) {
+ fence = toFence(parsed);
+ return line;
+ }
+ return line
+ .split(/(`+[^`]*`+)/g)
+ .map((part) =>
+ part.startsWith('`')
+ ? part
+ : part
+ .replace(/(\]\()([^\s)]+)(?=[\s)])/g, (_, before, url) => `${before}${mapUrl(url)}`)
+ .replace(
+ /^(\s*\[[^\]]+\]:\s*)(\S+)/,
+ (_, before, url) => `${before}${mapUrl(url)}`,
+ ),
+ )
+ .join('');
+ })
+ .join('\n');
+}
diff --git a/lib/metadata.ts b/lib/metadata.ts
index d39460dce..8da2eaa26 100644
--- a/lib/metadata.ts
+++ b/lib/metadata.ts
@@ -1,4 +1,5 @@
import type { Metadata } from 'next/types';
+import { docsPath, docsUrl, stripDocsPath } from '@/lib/docs-path';
const defaultMetadata: Metadata = {
title: 'Steel Docs',
@@ -8,11 +9,11 @@ const defaultMetadata: Metadata = {
title: 'Steel Docs',
description:
"Find all the guides and resources you need to build with Steel's browser automation platform",
- url: 'https://docs.steel.dev',
+ url: docsUrl('/'),
siteName: 'Steel Docs',
images: [
{
- url: '/images/logo.png',
+ url: docsPath('/images/logo.png'),
width: 800,
height: 600,
},
@@ -26,7 +27,7 @@ const defaultMetadata: Metadata = {
description:
"Find all the guides and resources you need to build with Steel's browser automation platform",
creator: '@steelsystems',
- images: ['/images/logo.png'],
+ images: [docsPath('/images/logo.png')],
},
};
@@ -38,13 +39,13 @@ const overviewMetadata: Partial = {
title: 'Steel Documentation',
description:
"Find all the guides and resources you need to build with Steel's browser automation platform.",
- images: [{ url: '/images/logo.png', width: 800, height: 600 }],
+ images: [{ url: docsPath('/images/logo.png'), width: 800, height: 600 }],
},
twitter: {
title: 'Steel Documentation',
description:
"Find all the guides and resources you need to build with Steel's browser automation platform.",
- images: ['/images/logo.png'],
+ images: [docsPath('/images/logo.png')],
},
};
@@ -54,12 +55,12 @@ const integrationsMetadata: Partial = {
openGraph: {
title: 'Integrations',
description: 'Learn how to integrate Steel with popular browser agents and automation tools.',
- images: [{ url: '/images/logo.png', width: 800, height: 600 }],
+ images: [{ url: docsPath('/images/logo.png'), width: 800, height: 600 }],
},
twitter: {
title: 'Integrations',
description: 'Learn how to integrate Steel with popular browser agents and automation tools.',
- images: ['/images/logo.png'],
+ images: [docsPath('/images/logo.png')],
},
};
@@ -69,12 +70,12 @@ const cookbookMetadata: Partial = {
openGraph: {
title: 'Cookbook',
description: 'Practical recipes and examples for automating browsers and workflows with Steel.',
- images: [{ url: '/images/logo.png', width: 800, height: 600 }],
+ images: [{ url: docsPath('/images/logo.png'), width: 800, height: 600 }],
},
twitter: {
title: 'Cookbook',
description: 'Practical recipes and examples for automating browsers and workflows with Steel.',
- images: ['/images/logo.png'],
+ images: [docsPath('/images/logo.png')],
},
};
@@ -84,12 +85,12 @@ const changelogMetadata: Partial = {
openGraph: {
title: 'Changelog',
description: 'Stay up to date with the latest features, improvements, and fixes in Steel.',
- images: [{ url: '/images/logo.png', width: 800, height: 600 }],
+ images: [{ url: docsPath('/images/logo.png'), width: 800, height: 600 }],
},
twitter: {
title: 'Changelog',
description: 'Stay up to date with the latest features, improvements, and fixes in Steel.',
- images: ['/images/logo.png'],
+ images: [docsPath('/images/logo.png')],
},
};
@@ -114,6 +115,7 @@ export const baseUrl =
: new URL(`https://${process.env.NEXT_PUBLIC_VERCEL_URL!}`);
export function getRouteMetadata(path: string): Partial {
+ path = stripDocsPath(path);
if (path.startsWith('/overview')) return overviewMetadata;
if (path.startsWith('/integrations')) return integrationsMetadata;
if (path.startsWith('/cookbook')) return cookbookMetadata;
diff --git a/lib/remark-docs-path.ts b/lib/remark-docs-path.ts
new file mode 100644
index 000000000..a5f7bf5ff
--- /dev/null
+++ b/lib/remark-docs-path.ts
@@ -0,0 +1,26 @@
+// ABOUTME: Remark plugin that adds the docs path prefix to rendered MDX links and media.
+// ABOUTME: It changes link, image and JSX attribute URLs only, never prose or code examples.
+import type { Root } from 'mdast';
+import { visit } from 'unist-util-visit';
+import { docsPath } from './docs-path';
+
+/** Rewrite rendered links/media, never prose or code examples in the source. */
+export function remarkDocsPath() {
+ return (tree: Root) => {
+ visit(tree, (node) => {
+ if (node.type === 'link' || node.type === 'image' || node.type === 'definition') {
+ node.url = docsPath(node.url);
+ }
+ if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
+ for (const attribute of node.attributes) {
+ if (
+ attribute.type === 'mdxJsxAttribute' &&
+ ['href', 'src', 'poster'].includes(attribute.name) &&
+ typeof attribute.value === 'string'
+ )
+ attribute.value = docsPath(attribute.value);
+ }
+ }
+ });
+ };
+}
diff --git a/lib/source.ts b/lib/source.ts
index d16fb6370..b74d71f70 100644
--- a/lib/source.ts
+++ b/lib/source.ts
@@ -7,6 +7,7 @@ import { icons as lucideIcons } from 'lucide-react';
import type { ThemeRegistrationResolved } from 'shiki';
import { docs } from '@/.source';
import { API, create, Js, Py } from '@/components/ui/icon';
+import { docsPath, stripDocsPath } from '@/lib/docs-path';
import { extractTagsAndLabels } from './utils/frontmatter-parser';
import type { FilterablePage } from './utils/tag-filtering';
@@ -239,7 +240,7 @@ function getFolderMeta(folderPath: string): Record {
*/
function extractSectionFromUrl(url: string): string {
// Remove leading slash and split by slash
- const pathParts = url.replace(/^\//, '').split('/');
+ const pathParts = stripDocsPath(url).replace(/^\//, '').split('/');
if (pathParts.length >= 2) {
return pathParts[1];
@@ -639,6 +640,7 @@ export const source = loader({
},
attachFile: (node, file) => {
let processedNode = attachFile(node, file);
+ if (processedNode.type === 'page') processedNode.url = docsPath(processedNode.url);
// Fumadocs' source loader doesn't set `$id` on external-link nodes
// (the `[Label](url)` meta.json syntax), which makes React flag the
@@ -765,7 +767,7 @@ export const source = loader({
return processedNode;
},
},
- baseUrl: '/',
+ baseUrl: process.env.NEXT_PUBLIC_DOCS_PATH_PREFIX || '/',
source: docs.toFumadocsSource(),
icon(icon) {
if (icon && icon in icons) return create({ icon: icons[icon as keyof typeof icons] });
diff --git a/lib/structured-data.ts b/lib/structured-data.ts
index 42cf8d446..2d69b9bb8 100644
--- a/lib/structured-data.ts
+++ b/lib/structured-data.ts
@@ -1,6 +1,8 @@
// ABOUTME: Shared schema.org entity IDs and pure builders for Steel documentation pages.
// ABOUTME: Keeps site identity and page references consistent across JSON-LD renderers.
-export const DOCS_URL = 'https://docs.steel.dev';
+import { DOCS_ORIGIN, DOCS_PATH_PREFIX, docsUrl, stripDocsPath } from '@/lib/docs-path';
+
+export const DOCS_URL = `${DOCS_ORIGIN}${DOCS_PATH_PREFIX}`;
export const STEEL_URL = 'https://steel.dev/';
export const STEEL_ORGANIZATION_ID = `${DOCS_URL}/#organization`;
export const DOCS_WEBSITE_ID = `${DOCS_URL}/#website`;
@@ -23,7 +25,8 @@ interface TechArticleSchemaOptions extends WebPageSchemaOptions {
}
export function getCanonicalPageUrl(path: string): string {
- return path === '/' ? `${DOCS_URL}/` : `${DOCS_URL}${path}`;
+ path = stripDocsPath(path);
+ return path === '/' ? docsUrl('/') : `${DOCS_URL}${path}`;
}
export function getWebPageId(path: string): string {
@@ -51,7 +54,7 @@ export function buildSiteIdentitySchema() {
{
'@type': 'WebSite',
'@id': DOCS_WEBSITE_ID,
- url: `${DOCS_URL}/`,
+ url: docsUrl('/'),
name: DOCS_SITE_NAME,
description: DOCS_SITE_DESCRIPTION,
publisher: { '@id': STEEL_ORGANIZATION_ID },
diff --git a/microfrontends.json b/microfrontends.json
new file mode 100644
index 000000000..a5e4a1863
--- /dev/null
+++ b/microfrontends.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://openapi.vercel.sh/microfrontends.json",
+ "applications": {
+ "steel-web": {
+ "development": { "local": 3000, "fallback": "steel.dev" }
+ },
+ "docs": {
+ "packageName": "steel-docs",
+ "development": { "local": 3030 },
+ "routing": [{ "group": "docs", "paths": ["/docs", "/docs/:path*"] }]
+ }
+ }
+}
diff --git a/middleware.ts b/middleware.ts
index d49d1216f..de8817cf2 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,5 +1,6 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
+import { DOCS_PATH_PREFIX, stripDocsPath } from '@/lib/docs-path';
import {
appendMarkdownVaryHeader,
isNegotiableDocsPath,
@@ -13,11 +14,18 @@ function isNegotiableMethod(method: string): boolean {
function withMarkdownVary(response: NextResponse): NextResponse {
appendMarkdownVaryHeader(response.headers);
+ if (DOCS_PATH_PREFIX) {
+ // Negotiated HTML/Markdown must not share a CDN cache entry. Keep Next's
+ // router Vary tokens; do not assume Vary alone configures Vercel's cache.
+ response.headers.set('Cache-Control', 'private, no-store');
+ response.headers.set('CDN-Cache-Control', 'no-store');
+ response.headers.set('Vercel-CDN-Cache-Control', 'no-store');
+ }
return response;
}
export default function middleware(request: NextRequest) {
- const { pathname } = request.nextUrl;
+ const pathname = stripDocsPath(request.nextUrl.pathname);
if (!isNegotiableMethod(request.method)) {
return NextResponse.next();
diff --git a/next.config.mjs b/next.config.mjs
index 4bbb67f8d..976a870e0 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,8 @@
import { createMDX } from "fumadocs-mdx/next";
+import { withMicrofrontends } from "@vercel/microfrontends/next/config";
+
+const prefix = process.env.NEXT_PUBLIC_DOCS_PATH_PREFIX || "";
+if (prefix !== "" && prefix !== "/docs") throw new Error("Invalid docs path prefix");
const withMDX = createMDX();
@@ -10,7 +14,10 @@ const config = {
// Add any Turbopack-specific options here (currently optional)
},
async rewrites() {
- return [];
+ return prefix ? { beforeFiles: [
+ { source: `${prefix}/.well-known/api-catalog`, destination: "/api/docs-catalog" },
+ { source: `${prefix}/:path*`, destination: "/:path*" },
+ ] } : [];
},
redirects: async () => {
return [
@@ -305,4 +312,24 @@ const config = {
},
};
-export default withMDX(config);
+if (prefix) {
+ const redirects = config.redirects;
+ config.redirects = async () => (await redirects()).map((route) => ({
+ ...route,
+ source: `${prefix}${route.source}`,
+ destination: route.destination.startsWith('/') ? `${prefix}${route.destination === '/' ? '' : route.destination}` : route.destination,
+ }));
+ const headers = config.headers;
+ config.headers = async () => (await headers()).flatMap((route) => {
+ const mapped = {
+ ...route,
+ source: `${prefix}${route.source}`,
+ headers: route.headers.map((header) => header.key === 'Link'
+ ? { ...header, value: header.value.replaceAll('', `<${prefix}/`) }
+ : header),
+ };
+ return route.source === '/(.*)' ? [mapped, { ...mapped, source: prefix }] : [mapped];
+ });
+}
+
+export default prefix ? withMicrofrontends(withMDX(config)) : withMDX(config);
diff --git a/package.json b/package.json
index 2781ebde1..aa2ffd98a 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"generate-openapi": "bun run ./scripts/fetch-openapi-specs.mts",
"generate-llms": "bun run ./scripts/generate-llms-txt.ts",
"generate-agent-skills": "bun run ./scripts/generate-agent-skills-index.ts",
+ "generate-docs-host-redirects": "bun run ./scripts/generate-docs-host-redirects.ts",
"sync-cookbook": "bun run ./scripts/sync-cookbook.ts",
"generate-changelog-draft": "bun run ./scripts/generate-changelog-draft.ts",
"preview-changelog": "bun run ./scripts/generate-changelog-draft.ts --preview",
@@ -71,6 +72,7 @@
"@radix-ui/react-tooltip": "^1.1.8",
"@tanstack/react-query": "^5.80.7",
"@types/js-yaml": "^4.0.9",
+ "@vercel/microfrontends": "2.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
diff --git a/app/favicon.ico b/public/favicon.ico
similarity index 100%
rename from app/favicon.ico
rename to public/favicon.ico
diff --git a/scripts/generate-agent-skills-index.ts b/scripts/generate-agent-skills-index.ts
index 6209a3bef..23b2fcee6 100644
--- a/scripts/generate-agent-skills-index.ts
+++ b/scripts/generate-agent-skills-index.ts
@@ -6,6 +6,7 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { gzipSync } from 'node:zlib';
import matter from 'gray-matter';
+import { docsPath } from '../lib/docs-path';
/** Discovery schema this index conforms to: cloudflare/agent-skills-discovery-rfc. */
export const AGENT_SKILLS_SCHEMA = 'https://schemas.agentskills.io/discovery/0.2.0/schema.json';
@@ -182,7 +183,7 @@ export function buildAgentSkillsIndex(artifacts: SkillArtifact[]): AgentSkillsIn
name: artifact.name,
type: 'archive' as const,
description: artifact.description,
- url: `/.well-known/agent-skills/${artifact.name}.tar.gz`,
+ url: docsPath(`/.well-known/agent-skills/${artifact.name}.tar.gz`),
digest: `sha256:${createHash('sha256').update(artifact.content).digest('hex')}`,
};
}),
diff --git a/scripts/generate-docs-host-redirects.ts b/scripts/generate-docs-host-redirects.ts
new file mode 100644
index 000000000..b3830b6a9
--- /dev/null
+++ b/scripts/generate-docs-host-redirects.ts
@@ -0,0 +1,120 @@
+#!/usr/bin/env bun
+// ABOUTME: Writes the docs-host-redirect project: vercel.json redirects, robots.txt and sitemap.xml.
+// ABOUTME: Run after `bun run generate`; `--check` fails when the files in the repository are stale.
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+import {
+ buildHostRedirects,
+ type Redirect,
+ renderOldRobots,
+ renderOldSitemap,
+ renderVercelJson,
+} from '../lib/docs-host-redirects';
+import { DOCS_PATH_PREFIX } from '../lib/docs-path';
+import { shouldIncludeLLMPage } from '../lib/get-llm-text';
+import { source } from '../lib/source';
+
+const ROOT = path.join(import.meta.dir, '..');
+const OUTPUT = path.join(ROOT, 'docs-host-redirect');
+
+/** Paths that app routes serve. Files in `public/` are found on disk. */
+export const APP_ROUTES = [
+ '/AGENTS.md',
+ '/DESIGN.md',
+ '/api/search',
+ '/changelog',
+ '/llms-full.txt',
+ '/llms.txt',
+];
+
+/** Redirects that app pages make, which next.config.mjs does not list. */
+export const APP_PAGE_REDIRECTS: Redirect[] = [
+ // app/(home)/overview/page.tsx
+ { source: '/overview', destination: '/', permanent: true },
+];
+
+const rootUrl = (url: string) => url.replace(/^\/en(\/|$)/, '/');
+
+export function rootPageUrls(): string[] {
+ return source.getPages().map((page) => rootUrl(page.url));
+}
+
+/** Pages that serve Markdown at `.md`, as app/llms.mdx does. */
+export function rootMarkdownPageUrls(): string[] {
+ return source
+ .getPages()
+ .filter(shouldIncludeLLMPage)
+ .map((page) => rootUrl(page.url));
+}
+
+export function listPublicFiles(directory = path.join(ROOT, 'public')): string[] {
+ const files: string[] = [];
+ const walk = (current: string) => {
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
+ const absolute = path.join(current, entry.name);
+ if (entry.isDirectory()) walk(absolute);
+ else files.push(`/${path.relative(directory, absolute).split(path.sep).join('/')}`);
+ }
+ };
+ walk(directory);
+ return files.sort();
+}
+
+export async function nextConfigRedirects(): Promise {
+ const config = (await import('../next.config.mjs')).default;
+ const redirects: { source: string; destination: string; permanent?: boolean }[] =
+ (await config.redirects?.()) ?? [];
+ return redirects.map(({ source, destination, permanent }) => ({
+ source,
+ destination,
+ permanent: permanent ?? false,
+ }));
+}
+
+async function main() {
+ if (DOCS_PATH_PREFIX) {
+ throw new Error('Run this script in root mode: unset NEXT_PUBLIC_DOCS_PATH_PREFIX');
+ }
+ const publicFiles = listPublicFiles();
+ if (!publicFiles.includes('/llms.txt')) {
+ throw new Error('public/llms.txt is missing. Run `bun run generate` first.');
+ }
+ const pages = rootPageUrls();
+ const legacy = [...(await nextConfigRedirects()), ...APP_PAGE_REDIRECTS];
+ const redirects = buildHostRedirects({
+ pages,
+ markdownPages: rootMarkdownPageUrls(),
+ publicFiles,
+ routes: APP_ROUTES,
+ legacy,
+ });
+ // A page that an old redirect hides is not a page on the old host.
+ const legacySources = new Set(legacy.map((redirect) => redirect.source));
+ const oldPages = pages.filter((page) => !legacySources.has(page));
+
+ const outputs: [string, string][] = [
+ [path.join(OUTPUT, 'vercel.json'), renderVercelJson(redirects)],
+ [path.join(OUTPUT, 'public', 'robots.txt'), renderOldRobots()],
+ [path.join(OUTPUT, 'public', 'sitemap.xml'), renderOldSitemap(oldPages)],
+ ];
+
+ if (process.argv.includes('--check')) {
+ const stale = outputs.filter(
+ ([file, content]) => !fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== content,
+ );
+ for (const [file] of stale) console.error(`Stale: ${path.relative(ROOT, file)}`);
+ if (stale.length > 0) process.exit(1);
+ console.log(`docs-host-redirect is current: ${redirects.length} redirects`);
+ return;
+ }
+
+ for (const [file, content] of outputs) {
+ fs.mkdirSync(path.dirname(file), { recursive: true });
+ fs.writeFileSync(file, content);
+ }
+ console.log(`Wrote docs-host-redirect: ${redirects.length} redirects, ${oldPages.length} pages`);
+}
+
+if (import.meta.main) {
+ await main();
+}
diff --git a/scripts/generate-llms-txt.ts b/scripts/generate-llms-txt.ts
index e611d185b..0af2fe965 100644
--- a/scripts/generate-llms-txt.ts
+++ b/scripts/generate-llms-txt.ts
@@ -3,6 +3,7 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import matter from 'gray-matter';
import { AGENT_INSTRUCTIONS } from '../lib/agent-instructions';
+import { DOCS_ORIGIN, DOCS_PATH_PREFIX, stripDocsPath } from '../lib/docs-path';
import { source } from '../lib/source';
const PUBLIC_DIR = path.join(process.cwd(), 'public');
@@ -16,7 +17,7 @@ interface GenerationConfig {
// Environment variables (optional):
// LLMS_BASE_URL - URL for llms.txt (default: "https://docs.steel.dev")
const config: GenerationConfig = {
- productionUrl: process.env.LLMS_BASE_URL || 'https://docs.steel.dev',
+ productionUrl: process.env.LLMS_BASE_URL || `${DOCS_ORIGIN}${DOCS_PATH_PREFIX}`,
};
interface PageMetadata {
@@ -54,11 +55,12 @@ function getAllPages(): PageMetadata[] {
.filter(shouldIncludePage)
.map((page) => {
// Extract section from URL (split by / and filter out empty strings)
- const urlParts = page.url.split('/').filter(Boolean);
+ const internalPath = stripDocsPath(page.url);
+ const urlParts = internalPath.split('/').filter(Boolean);
const section = urlParts.slice(0, -1); // All parts except the last one
// Clean URL for content links (remove locale prefix)
- let cleanUrl = page.url;
+ let cleanUrl = internalPath;
const locales = ['en', 'es']; // Add your supported locales here
if (urlParts.length > 0 && locales.includes(urlParts[0])) {
// Remove the locale prefix for content links
@@ -93,7 +95,7 @@ function getAllPages(): PageMetadata[] {
return {
title,
description: ((frontmatter as any)?.description as string | undefined) || '',
- url: page.url, // Keep original URL for file path generation
+ url: internalPath, // File paths remain unprefixed; routing owns /docs.
cleanUrl: cleanUrl, // Add clean URL for content links
section: section,
};
diff --git a/scripts/verify-docs-browser.ts b/scripts/verify-docs-browser.ts
new file mode 100644
index 000000000..ddd0784d6
--- /dev/null
+++ b/scripts/verify-docs-browser.ts
@@ -0,0 +1,91 @@
+// ABOUTME: Drives desktop and mobile browsers through a composed docs preview with Playwright.
+// ABOUTME: It fails on JS errors, failed assets, or docs links that leave /docs.
+import { mkdir } from 'node:fs/promises';
+import { chromium } from 'playwright';
+
+const origin = process.argv[2];
+if (!origin) throw new Error('Usage: bun scripts/verify-docs-browser.ts ');
+const bypass = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
+const browser = await chromium.launch();
+await mkdir('.vercel/qa', { recursive: true });
+try {
+ for (const [name, width, height] of [
+ ['desktop', 1440, 1000],
+ ['mobile', 390, 844],
+ ] as const) {
+ const page = await browser.newPage({ viewport: { width, height } });
+ const errors: string[] = [];
+ const rsc: string[] = [];
+ page.on('pageerror', (error) => errors.push(error.message));
+ page.on('response', (response) => {
+ if (
+ new URL(response.url()).origin === origin &&
+ response.status() >= 400 &&
+ ['script', 'stylesheet', 'image', 'font'].includes(response.request().resourceType())
+ ) {
+ errors.push(`Asset ${response.status()}: ${new URL(response.url()).pathname}`);
+ }
+ if (response.headers()['content-type']?.includes('text/x-component') && response.ok()) {
+ rsc.push(response.url());
+ }
+ });
+ await page.route('**/*', (route) => {
+ const headers = route.request().headers();
+ if (new URL(route.request().url()).origin === origin && bypass) {
+ headers['x-vercel-protection-bypass'] = bypass;
+ } else {
+ delete headers['x-vercel-protection-bypass'];
+ }
+ return route.continue({ headers });
+ });
+ await page.goto(`${origin}/docs/overview/steel-cli`);
+ await page.getByRole('heading', { name: 'Steel CLI', exact: true }).waitFor();
+ await page.waitForFunction(() => {
+ const anchor = document.querySelector('a[href="/docs/overview/authentication"]');
+ return anchor && Object.keys(anchor).some((key) => key.startsWith('__reactFiber$'));
+ });
+ if (name === 'mobile') {
+ await page.getByRole('button', { name: 'Open navigation' }).click();
+ }
+ await page.locator('a[href="/docs/overview/authentication"]:visible').first().click();
+ await page.waitForURL('**/docs/overview/authentication');
+ await page.getByRole('heading', { name: 'Authentication', exact: true }).waitFor();
+ if (name === 'mobile') await page.locator('[aria-label="Search"]:visible').first().click();
+ else
+ await page.getByText('Search...', { exact: true }).filter({ visible: true }).first().click();
+ const dialog = page.getByRole('dialog');
+ await dialog.locator('input').fill('Steel CLI');
+ await dialog.getByRole('button', { name: 'Steel CLI', exact: true }).first().click();
+ await page.waitForURL('**/docs/overview/steel-cli*');
+ await page.getByRole('heading', { name: 'Steel CLI', exact: true }).waitFor();
+ const anchor = await page.locator('article h2[id]').first().getAttribute('id');
+ if (!anchor) throw new Error('Expected a heading anchor');
+ await page.goto(`${origin}/docs/overview/steel-cli#${encodeURIComponent(anchor)}`);
+ await page.reload();
+ if (
+ decodeURIComponent(new URL(page.url()).hash.slice(1)) !== anchor ||
+ !(await page.locator(`[id="${anchor}"]`).count())
+ ) {
+ throw new Error('Heading anchor did not survive navigation/reload');
+ }
+ await page.screenshot({ path: `.vercel/qa/docs-${name}.png`, fullPage: true });
+ const escapes = await page
+ .locator('a[href]')
+ .evaluateAll((links) =>
+ links
+ .map((link) => link.getAttribute('href') || '')
+ .filter(
+ (href) => href.startsWith('/') && !href.startsWith('/docs') && !href.startsWith('//'),
+ ),
+ );
+ if (errors.length || escapes.length || !rsc.length) {
+ throw new Error(JSON.stringify({ name, errors, escapes, rsc }));
+ }
+ console.log(
+ JSON.stringify({ name, url: page.url(), rscResponses: rsc.length, errors, escapes }),
+ );
+ await page.close();
+ }
+} finally {
+ await browser.close();
+}
diff --git a/scripts/verify-docs-host-redirect.ts b/scripts/verify-docs-host-redirect.ts
new file mode 100644
index 000000000..4bc80967e
--- /dev/null
+++ b/scripts/verify-docs-host-redirect.ts
@@ -0,0 +1,100 @@
+#!/usr/bin/env bun
+// ABOUTME: Checks a deployment of the docs-host-redirect project against its vercel.json map.
+// ABOUTME: Usage: bun scripts/verify-docs-host-redirect.ts , before and after the domain move.
+import { readFileSync } from 'node:fs';
+import * as path from 'node:path';
+
+interface MapEntry {
+ source: string;
+ destination: string;
+ permanent: boolean;
+}
+
+/** A query that each request carries, to prove that redirects keep the query string. */
+export const PROBE_QUERY = 'utm_source=redirect-check';
+
+/**
+ * Gives the problem with a redirect `Location`, or null. The location must have the
+ * destination's origin, path and fragment, and every query parameter of the destination
+ * and of the request.
+ */
+export function locationProblem(
+ location: string | null,
+ destination: string,
+ query: string,
+): string | null {
+ if (!location) return 'no Location header';
+ const actual = new URL(location);
+ const expected = new URL(destination);
+ if (`${actual.origin}${actual.pathname}` !== `${expected.origin}${expected.pathname}`) {
+ return `Location ${location} is not ${destination}`;
+ }
+ if (actual.hash !== expected.hash) return `fragment ${actual.hash} is not ${expected.hash}`;
+ const wanted = new URLSearchParams(expected.search);
+ for (const [key, value] of new URLSearchParams(query)) wanted.append(key, value);
+ for (const [key, value] of wanted) {
+ if (!actual.searchParams.getAll(key).includes(value)) return `query ${key}=${value} lost`;
+ }
+ return null;
+}
+
+async function main(baseUrl: string) {
+ const file = path.join(import.meta.dir, '..', 'docs-host-redirect', 'vercel.json');
+ const { redirects } = JSON.parse(readFileSync(file, 'utf8')) as { redirects: MapEntry[] };
+ const bypass = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
+ const headers: Record = bypass ? { 'x-vercel-protection-bypass': bypass } : {};
+ const get = (pathname: string) =>
+ fetch(new URL(pathname, baseUrl), { headers, redirect: 'manual' });
+ const failures: string[] = [];
+
+ const queue = [...redirects];
+ await Promise.all(
+ Array.from({ length: 8 }, async () => {
+ for (let entry = queue.shift(); entry; entry = queue.shift()) {
+ const source = entry.source.replace(/\\(.)/g, '$1');
+ const response = await get(`${source}?${PROBE_QUERY}`);
+ const status = entry.permanent ? 308 : 307;
+ if (response.status !== status) {
+ failures.push(`${source}: expected ${status}, got ${response.status}`);
+ continue;
+ }
+ const problem = locationProblem(
+ response.headers.get('location'),
+ entry.destination,
+ PROBE_QUERY,
+ );
+ if (problem) failures.push(`${source}: ${problem}`);
+ }
+ }),
+ );
+
+ const slash = await get('/overview/steel-cli/');
+ if (slash.status !== 308 || slash.headers.get('location') !== '/overview/steel-cli') {
+ failures.push(`/overview/steel-cli/: ${slash.status} ${slash.headers.get('location')}`);
+ }
+ for (const unknown of ['/migration-definitely-missing', '/docs/overview/steel-cli']) {
+ const response = await get(unknown);
+ if (response.status !== 404) failures.push(`${unknown}: expected 404, got ${response.status}`);
+ }
+ const robots = await get('/robots.txt');
+ if (robots.status !== 200 || !(await robots.text()).includes('Allow: /')) {
+ failures.push(`/robots.txt: ${robots.status}`);
+ }
+ const sitemap = await get('/sitemap.xml');
+ if (sitemap.status !== 200 || !(await sitemap.text()).includes('https://docs.steel.dev/')) {
+ failures.push(`/sitemap.xml: ${sitemap.status}`);
+ }
+
+ if (failures.length > 0) {
+ for (const failure of failures.sort()) console.error(failure);
+ console.error(`docs-host-redirect verification failed: ${failures.length} findings`);
+ process.exit(1);
+ }
+ console.log(`docs-host-redirect verified: ${redirects.length} redirects, 404s, robots, sitemap.`);
+}
+
+if (import.meta.main) {
+ const baseUrl = process.argv[2];
+ if (!baseUrl) throw new Error('Usage: bun scripts/verify-docs-host-redirect.ts ');
+ await main(baseUrl);
+}
diff --git a/source.config.ts b/source.config.ts
index 20f55746e..e052f699b 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -1,20 +1,13 @@
-import {
- type CodeHikeConfig,
- recmaCodeHike,
- remarkCodeHike,
-} from "codehike/mdx";
-import {
- defineConfig,
- defineDocs,
- frontmatterSchema,
-} from "fumadocs-mdx/config";
-import remarkDirective from "remark-directive";
-import { z } from "zod";
-import { remarkCustomDirectives } from "./lib/remark-custom-directives";
-import { remarkFormatCode } from "./lib/remark-format-code";
+import { type CodeHikeConfig, recmaCodeHike, remarkCodeHike } from 'codehike/mdx';
+import { defineConfig, defineDocs, frontmatterSchema } from 'fumadocs-mdx/config';
+import remarkDirective from 'remark-directive';
+import { z } from 'zod';
+import { remarkCustomDirectives } from './lib/remark-custom-directives';
+import { remarkDocsPath } from './lib/remark-docs-path';
+import { remarkFormatCode } from './lib/remark-format-code';
export const docs = defineDocs({
- dir: "content/docs",
+ dir: 'content/docs',
docs: {
schema: frontmatterSchema.extend({
llm: z.boolean().optional(),
@@ -49,8 +42,8 @@ export const docs = defineDocs({
const chConfig: CodeHikeConfig = {
components: {
- code: "DocsKitCode",
- inlineCode: "DocsKitInlineCode",
+ code: 'DocsKitCode',
+ inlineCode: 'DocsKitInlineCode',
},
};
@@ -62,6 +55,7 @@ export default defineConfig({
remarkCustomDirectives,
remarkFormatCode,
...v,
+ remarkDocsPath,
],
recmaPlugins: [[recmaCodeHike, chConfig]],
},
diff --git a/tests/docs-host-redirects.test.ts b/tests/docs-host-redirects.test.ts
new file mode 100644
index 000000000..911160fcc
--- /dev/null
+++ b/tests/docs-host-redirects.test.ts
@@ -0,0 +1,225 @@
+// ABOUTME: Tests the docs.steel.dev redirect map: the pure builder and the checked-in project files.
+// ABOUTME: The coverage tests fail when a page, a public file or an old redirect has no entry.
+import { describe, expect, test } from 'bun:test';
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import {
+ buildHostRedirects,
+ escapeSource,
+ MAX_VERCEL_REDIRECTS,
+ NEW_DOCS_URL,
+ newDocsUrl,
+ renderOldRobots,
+ renderOldSitemap,
+} from '@/lib/docs-host-redirects';
+import {
+ APP_PAGE_REDIRECTS,
+ APP_ROUTES,
+ nextConfigRedirects,
+ rootMarkdownPageUrls,
+ rootPageUrls,
+} from '@/scripts/generate-docs-host-redirects';
+import { locationProblem } from '@/scripts/verify-docs-host-redirect';
+
+const ROOT = fileURLToPath(new URL('..', import.meta.url));
+const read = (path: string) => readFileSync(`${ROOT}${path}`, 'utf8');
+
+interface VercelRedirect {
+ source: string;
+ destination: string;
+ permanent: boolean;
+}
+
+const vercelJson = JSON.parse(read('docs-host-redirect/vercel.json')) as {
+ redirects: VercelRedirect[];
+ trailingSlash: boolean;
+ outputDirectory: string;
+};
+const unescapeSource = (source: string) => source.replace(/\\(.)/g, '$1');
+const checkedIn = new Map(
+ vercelJson.redirects.map((redirect) => [unescapeSource(redirect.source), redirect]),
+);
+
+describe('newDocsUrl', () => {
+ test('maps the docs home without a trailing slash', () => {
+ expect(newDocsUrl('/')).toBe('https://steel.dev/docs');
+ });
+
+ test('keeps the path, the query and the fragment', () => {
+ expect(newDocsUrl('/overview/steel-cli')).toBe('https://steel.dev/docs/overview/steel-cli');
+ expect(newDocsUrl('/cookbook/playwright#python')).toBe(
+ 'https://steel.dev/docs/cookbook/playwright#python',
+ );
+ expect(newDocsUrl('/#top')).toBe('https://steel.dev/docs#top');
+ });
+
+ test('refuses URLs that are not root-relative paths', () => {
+ expect(() => newDocsUrl('https://example.com/a')).toThrow();
+ expect(() => newDocsUrl('//example.com/a')).toThrow();
+ });
+});
+
+describe('escapeSource', () => {
+ test('escapes path-to-regexp syntax and keeps other characters', () => {
+ expect(escapeSource('/a:b(c)*+?{d}')).toBe('/a\\:b\\(c\\)\\*\\+\\?\\{d\\}');
+ expect(escapeSource('/images/a_b-c.png')).toBe('/images/a_b-c.png');
+ });
+});
+
+describe('buildHostRedirects', () => {
+ const build = (legacy = APP_PAGE_REDIRECTS) =>
+ buildHostRedirects({
+ pages: ['/overview/steel-cli', '/overview', '/cookbook/authors/a'],
+ markdownPages: ['/overview/steel-cli', '/overview'],
+ publicFiles: ['/images/logo.png', '/robots.txt'],
+ routes: ['/llms.txt', '/sitemap.xml'],
+ legacy,
+ });
+
+ test('gives each page its HTML and Open Graph URLs, and Markdown pages their .md URL', () => {
+ const sources = build().map((redirect) => redirect.source);
+ expect(sources).toContain('/overview/steel-cli');
+ expect(sources).toContain('/overview/steel-cli.md');
+ expect(sources).toContain('/og/overview/steel-cli');
+ expect(sources).toContain('/og/cookbook/authors/a');
+ expect(sources).not.toContain('/cookbook/authors/a.md');
+ });
+
+ test('lets an old redirect win over a page at the same path', () => {
+ const redirects = build();
+ const overview = redirects.find((redirect) => redirect.source === '/overview');
+ expect(overview?.destination).toBe(NEW_DOCS_URL);
+ const sources = redirects.map((redirect) => redirect.source);
+ expect(sources).not.toContain('/overview.md');
+ expect(sources).toContain('/og/overview');
+ });
+
+ test('leaves robots.txt and sitemap.xml to the redirect project', () => {
+ const sources = build().map((redirect) => redirect.source);
+ expect(sources).not.toContain('/robots.txt');
+ expect(sources).not.toContain('/sitemap.xml');
+ });
+
+ test('follows chains to the final destination in one hop', () => {
+ const redirects = build([
+ { source: '/a', destination: '/b', permanent: true },
+ { source: '/b', destination: '/c#x', permanent: true },
+ { source: '/t', destination: '/a', permanent: false },
+ { source: '/out', destination: 'https://example.com/', permanent: true },
+ ]);
+ const bySource = new Map(redirects.map((redirect) => [redirect.source, redirect]));
+ expect(bySource.get('/a')?.destination).toBe(`${NEW_DOCS_URL}/c#x`);
+ expect(bySource.get('/t')).toEqual({
+ source: '/t',
+ destination: `${NEW_DOCS_URL}/c#x`,
+ permanent: false,
+ });
+ expect(bySource.get('/out')?.destination).toBe('https://example.com/');
+ });
+
+ test('refuses a redirect loop', () => {
+ expect(() =>
+ build([
+ { source: '/a', destination: '/b', permanent: true },
+ { source: '/b', destination: '/a', permanent: true },
+ ]),
+ ).toThrow('Redirect loop');
+ });
+});
+
+describe('checked-in docs-host-redirect project', () => {
+ test('stays inside the Vercel redirect limit and has no duplicate sources', () => {
+ expect(vercelJson.redirects.length).toBeLessThanOrEqual(MAX_VERCEL_REDIRECTS);
+ expect(checkedIn.size).toBe(vercelJson.redirects.length);
+ expect(vercelJson.trailingSlash).toBe(false);
+ expect(vercelJson.outputDirectory).toBe('public');
+ });
+
+ test('sends every internal destination to steel.dev/docs and nowhere on the old host', () => {
+ for (const redirect of vercelJson.redirects) {
+ expect(redirect.destination.startsWith('https://')).toBe(true);
+ expect(redirect.destination).not.toContain('docs.steel.dev');
+ expect(redirect.destination).not.toContain('/docs/docs');
+ }
+ });
+
+ test('covers every page, tracked public file, app route and old redirect', async () => {
+ const trackedPublic = execFileSync('git', ['ls-files', 'public'], {
+ cwd: ROOT,
+ encoding: 'utf8',
+ })
+ .split('\n')
+ .filter(Boolean)
+ .map((file) => file.replace(/^public/, ''));
+ const expected = buildHostRedirects({
+ pages: rootPageUrls(),
+ markdownPages: rootMarkdownPageUrls(),
+ publicFiles: trackedPublic,
+ routes: APP_ROUTES,
+ legacy: [...(await nextConfigRedirects()), ...APP_PAGE_REDIRECTS],
+ });
+ const missing = expected.filter(
+ (redirect) =>
+ checkedIn.get(redirect.source)?.destination !== redirect.destination ||
+ checkedIn.get(redirect.source)?.permanent !== redirect.permanent,
+ );
+ expect(missing.map((redirect) => redirect.source)).toEqual([]);
+ });
+
+ test('covers every docs.steel.dev URL that the sitemap and Search Console showed', () => {
+ const known = read('tests/fixtures/docs-host-known-urls.txt')
+ .split('\n')
+ .filter((line) => line && !line.startsWith('#'));
+ const servedHere = ['/robots.txt', '/sitemap.xml'];
+ const uncovered = known.filter((path) => !checkedIn.has(path) && !servedHere.includes(path));
+ expect(uncovered).toEqual([]);
+ });
+
+ test('serves robots.txt and an old-URL sitemap that the generator wrote', () => {
+ expect(read('docs-host-redirect/public/robots.txt')).toBe(renderOldRobots());
+ const sitemap = read('docs-host-redirect/public/sitemap.xml');
+ expect(sitemap).toContain('https://docs.steel.dev/ ');
+ expect(sitemap).toContain('https://docs.steel.dev/overview/stealth/captcha-solving ');
+ expect(sitemap).not.toContain('steel.dev/docs');
+ expect(renderOldSitemap(['/a'])).toContain('https://docs.steel.dev/a ');
+ });
+});
+
+describe('locationProblem', () => {
+ const query = 'utm_source=redirect-check';
+
+ test('accepts the destination with the request query, before the fragment', () => {
+ expect(
+ locationProblem(
+ 'https://steel.dev/docs/cookbook/playwright?utm_source=redirect-check#python',
+ 'https://steel.dev/docs/cookbook/playwright#python',
+ query,
+ ),
+ ).toBeNull();
+ expect(
+ locationProblem(
+ 'https://render.com/deploy?image=steeldev%2Fsteel&utm_source=redirect-check',
+ 'https://render.com/deploy?image=steeldev/steel',
+ query,
+ ),
+ ).toBeNull();
+ });
+
+ test('finds a lost query, a lost fragment and a wrong path', () => {
+ expect(
+ locationProblem('https://steel.dev/docs/a', 'https://steel.dev/docs/a', query),
+ ).toContain('lost');
+ expect(
+ locationProblem(
+ 'https://steel.dev/docs/a?utm_source=redirect-check',
+ 'https://steel.dev/docs/a#b',
+ query,
+ ),
+ ).toContain('fragment');
+ expect(locationProblem('https://steel.dev/b', 'https://steel.dev/docs/a', query)).toContain(
+ 'is not',
+ );
+ expect(locationProblem(null, 'https://steel.dev/docs/a', query)).toBe('no Location header');
+ });
+});
diff --git a/tests/docs-path-transforms.test.ts b/tests/docs-path-transforms.test.ts
new file mode 100644
index 000000000..18eb65bbb
--- /dev/null
+++ b/tests/docs-path-transforms.test.ts
@@ -0,0 +1,71 @@
+// ABOUTME: Tests the MDX and Markdown link transforms in root mode and in /docs path mode.
+// ABOUTME: Each case runs in a child process so that the prefix environment variable applies.
+import { describe, expect, test } from 'bun:test';
+import { spawnSync } from 'node:child_process';
+
+function run(script: string, prefix = '/docs', origin = 'https://preview.example.com') {
+ return spawnSync(process.execPath, ['-e', script], {
+ cwd: `${import.meta.dir}/..`,
+ env: { ...process.env, NEXT_PUBLIC_DOCS_PATH_PREFIX: prefix, NEXT_PUBLIC_DOCS_ORIGIN: origin },
+ encoding: 'utf8',
+ });
+}
+
+describe('docs migration transforms', () => {
+ test('rewrites MDX links, media, definitions and JSX without changing code or external URLs', () => {
+ const result = run(`
+ import { remarkDocsPath } from './lib/remark-docs-path';
+ const tree = {type:'root',children:[
+ {type:'link',url:'/guide#anchor',children:[]},
+ {type:'image',url:'/images/a.png'},
+ {type:'definition',url:'https://docs.steel.dev/guide?q=1'},
+ {type:'link',url:'https://external.example/guide',children:[]},
+ {type:'code',value:'[Guide](/guide)'},
+ {type:'mdxJsxFlowElement',name:'Card',attributes:[{type:'mdxJsxAttribute',name:'href',value:'/guide'}],children:[]},
+ {type:'mdxJsxTextElement',name:'img',attributes:[{type:'mdxJsxAttribute',name:'src',value:'/images/b.png'}],children:[]}
+ ]};remarkDocsPath()(tree);console.log(JSON.stringify(tree.children));
+ `);
+ expect(result.status).toBe(0);
+ const nodes = JSON.parse(result.stdout);
+ expect(nodes.map((n: { url?: string }) => n.url).slice(0, 4)).toEqual([
+ '/docs/guide#anchor',
+ '/docs/images/a.png',
+ '/docs/guide?q=1',
+ 'https://external.example/guide',
+ ]);
+ expect(nodes[4].value).toBe('[Guide](/guide)');
+ expect(nodes[5].attributes[0].value).toBe('/docs/guide');
+ expect(nodes[6].attributes[0].value).toBe('/docs/images/b.png');
+ });
+
+ test('rewrites Markdown link destinations while preserving titles, fences and inline code', () => {
+ const input =
+ '[Guide](/guide "Title")\n[Owned](https://docs.steel.dev/guide#h)\n[Other](https://example.com)\n[ref]: /guide "Title"\n`[Code](/guide)`\n```md\n[Code](/guide)\n```';
+ const result = run(
+ `import {mapMarkdownLinks} from './lib/llm-markdown';import {docsPath} from './lib/docs-path';console.log(JSON.stringify(mapMarkdownLinks(${JSON.stringify(input)},docsPath)));`,
+ );
+ expect(result.status).toBe(0);
+ expect(JSON.parse(result.stdout)).toBe(
+ '[Guide](/docs/guide "Title")\n[Owned](/docs/guide#h)\n[Other](https://example.com)\n[ref]: /docs/guide "Title"\n`[Code](/guide)`\n```md\n[Code](/guide)\n```',
+ );
+ });
+
+ test('root-mode rendering transforms are unchanged', () => {
+ const result = run(
+ `import {docsPath} from './lib/docs-path';console.log(JSON.stringify(['/guide','https://docs.steel.dev/guide'].map(docsPath)));`,
+ '',
+ );
+ expect(result.status).toBe(0);
+ expect(JSON.parse(result.stdout)).toEqual(['/guide', 'https://docs.steel.dev/guide']);
+ });
+
+ for (const [prefix, origin] of [
+ ['/bad', 'https://example.com'],
+ ['/docs', 'https://example.com/path'],
+ ['/docs', 'https://user:pass@example.com'],
+ ]) {
+ test(`rejects invalid URL configuration ${prefix} ${origin}`, () => {
+ expect(run("import './lib/docs-path'", prefix, origin).status).not.toBe(0);
+ });
+ }
+});
diff --git a/tests/docs-path.test.ts b/tests/docs-path.test.ts
new file mode 100644
index 000000000..bedb71425
--- /dev/null
+++ b/tests/docs-path.test.ts
@@ -0,0 +1,72 @@
+// ABOUTME: Tests docsPath, stripDocsPath and docsUrl in root mode and in /docs path mode.
+// ABOUTME: Each case runs in a child process so that the prefix environment variable applies.
+import { describe, expect, test } from 'bun:test';
+import { spawnSync } from 'node:child_process';
+
+describe('docs public path boundary', () => {
+ for (const prefix of ['', '/docs']) {
+ test(`preserves URLs in ${prefix || 'root'} mode`, () => {
+ const script = `import { docsPath, stripDocsPath, docsUrl } from './lib/docs-path';
+ const paths = ['/', '/overview/steel-cli', '/api/search?q=steel', '/images/a.png', '/docs/a', '/a#heading', 'https://example.com/a', '//example.com/a', '#heading'];
+ console.log(JSON.stringify({paths: paths.map(docsPath), stripped: stripDocsPath('/docs/a'), url: docsUrl('/overview/steel-cli')}));`;
+ const result = spawnSync(process.execPath, ['-e', script], {
+ cwd: `${import.meta.dir}/..`,
+ env: {
+ ...process.env,
+ NEXT_PUBLIC_DOCS_PATH_PREFIX: prefix,
+ NEXT_PUBLIC_DOCS_ORIGIN: 'https://steel.dev',
+ },
+ encoding: 'utf8',
+ });
+ expect(result.status).toBe(0);
+ const value = JSON.parse(result.stdout);
+ expect(value.paths).toEqual([
+ prefix || '/',
+ `${prefix}/overview/steel-cli`,
+ `${prefix}/api/search?q=steel`,
+ `${prefix}/images/a.png`,
+ '/docs/a',
+ `${prefix}/a#heading`,
+ 'https://example.com/a',
+ '//example.com/a',
+ '#heading',
+ ]);
+ expect(value.stripped).toBe(prefix ? '/a' : '/docs/a');
+ expect(value.url).toBe(`https://steel.dev${prefix}/overview/steel-cli`);
+ });
+ }
+
+ const evaluate = (prefix: string, expression: string) =>
+ spawnSync(
+ process.execPath,
+ ['-e', `import * as p from './lib/docs-path'; console.log(JSON.stringify(${expression}));`],
+ {
+ cwd: `${import.meta.dir}/..`,
+ env: {
+ ...process.env,
+ NEXT_PUBLIC_DOCS_PATH_PREFIX: prefix,
+ NEXT_PUBLIC_DOCS_ORIGIN: 'https://steel.dev',
+ },
+ encoding: 'utf8',
+ },
+ );
+
+ test('gives the docs home without a trailing slash in path mode', () => {
+ const result = evaluate('/docs', "[p.docsUrl('/'), p.docsPath('/'), p.stripDocsPath('/docs')]");
+ expect(JSON.parse(result.stdout)).toEqual(['https://steel.dev/docs', '/docs', '/']);
+ });
+
+ test('moves absolute old-host links under the prefix in path mode only', () => {
+ const link = "p.docsPath('https://docs.steel.dev/overview/steel-cli?x=1#a')";
+ expect(JSON.parse(evaluate('/docs', link).stdout)).toBe('/docs/overview/steel-cli?x=1#a');
+ expect(JSON.parse(evaluate('', link).stdout)).toBe(
+ 'https://docs.steel.dev/overview/steel-cli?x=1#a',
+ );
+ });
+
+ test('refuses a prefix other than /docs', () => {
+ const result = evaluate('/documentation', "p.docsPath('/')");
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain('NEXT_PUBLIC_DOCS_PATH_PREFIX must be empty or /docs');
+ });
+});
diff --git a/tests/fixtures/docs-host-known-urls.txt b/tests/fixtures/docs-host-known-urls.txt
new file mode 100644
index 000000000..eda860853
--- /dev/null
+++ b/tests/fixtures/docs-host-known-urls.txt
@@ -0,0 +1,169 @@
+# ABOUTME: docs.steel.dev paths that served a page or a redirect on 2026-10-08: the live sitemap
+# ABOUTME: and every Search Console page (16 months) that did not return 404. The redirect map must cover each.
+/
+/api-reference
+/changelog
+/changelog/changelog-003
+/changelog/changelog-004
+/changelog/changelog-009
+/changelog/changelog-010
+/changelog/changelog-014
+/changelog/changelog-020
+/changelog/changelog-035
+/cookbook
+/cookbook/agentkit
+/cookbook/agno
+/cookbook/auth-context
+/cookbook/authors/aspectrr
+/cookbook/authors/bsparker
+/cookbook/authors/danew
+/cookbook/authors/hussufo
+/cookbook/authors/jagadeshjai
+/cookbook/authors/junhsss
+/cookbook/authors/nibzard
+/cookbook/browser-use
+/cookbook/browser-use-captcha-auto
+/cookbook/browser-use-captcha-manual
+/cookbook/chromedp
+/cookbook/chromiumoxide
+/cookbook/claude-agent-sdk
+/cookbook/claude-computer-use
+/cookbook/claude-computer-use-mobile
+/cookbook/convex-chat-with-page
+/cookbook/convex-price-watch
+/cookbook/credentials
+/cookbook/crewai
+/cookbook/deep-research
+/cookbook/eino
+/cookbook/extensions
+/cookbook/files
+/cookbook/gemini-computer-use
+/cookbook/genkit
+/cookbook/google-adk
+/cookbook/headless-chrome
+/cookbook/langchaingo
+/cookbook/langgraph
+/cookbook/magnitude
+/cookbook/mastra
+/cookbook/mcp
+/cookbook/microsoft-agent-framework
+/cookbook/notte
+/cookbook/openai-agents
+/cookbook/openai-computer-use
+/cookbook/playwright
+/cookbook/profiles
+/cookbook/puppeteer
+/cookbook/pydantic-ai
+/cookbook/restate-agent
+/cookbook/rig
+/cookbook/rod
+/cookbook/scrape
+/cookbook/selenium
+/cookbook/stagehand
+/cookbook/stagehand-ts
+/cookbook/swiftide
+/cookbook/temporal-browser-workflow
+/cookbook/topics/agents
+/cookbook/topics/authentication
+/cookbook/topics/browser-automation
+/cookbook/topics/browser-use
+/cookbook/topics/captchas
+/cookbook/topics/computer-use
+/cookbook/topics/convex
+/cookbook/topics/mcp
+/cookbook/topics/mobile
+/cookbook/topics/nextjs
+/cookbook/topics/playwright
+/cookbook/topics/restate
+/cookbook/topics/search
+/cookbook/topics/steel-apis
+/cookbook/topics/subagents
+/cookbook/topics/temporal
+/cookbook/topics/triggerdev
+/cookbook/topics/typed-output
+/cookbook/trigger-dev-browser-job
+/cookbook/vercel-ai-sdk
+/cookbook/vercel-ai-sdk-nextjs
+/cookbook/you-com-search
+/favicon.ico
+/integrations
+/integrations/agentkit
+/integrations/agno
+/integrations/ai-sdk
+/integrations/browser-use
+/integrations/claude-agent-sdk
+/integrations/claude-code
+/integrations/claude-computer-use
+/integrations/codex
+/integrations/crewai
+/integrations/gemini-computer-use
+/integrations/hermes-agent
+/integrations/langgraph
+/integrations/magnitude
+/integrations/mastra
+/integrations/notte
+/integrations/openai-agents-sdk
+/integrations/openai-computer-use
+/integrations/openclaw
+/integrations/pi-agent
+/integrations/playwright
+/integrations/puppeteer
+/integrations/pydantic-ai
+/integrations/replit
+/integrations/selenium
+/integrations/stackblitz-bolt.new
+/integrations/stagehand
+/integrations/x402
+/llms-full.txt
+/overview/agent-traces/api
+/overview/agent-traces/overview
+/overview/agent-traces/timeline
+/overview/authentication
+/overview/browser-tools/overview
+/overview/captchas-api/overview
+/overview/credentials-api/overview
+/overview/extensions-api/overview
+/overview/files-api/overview
+/overview/guides/playwright-node
+/overview/guides/playwright-python
+/overview/guides/puppeteer
+/overview/intro-to-steel
+/overview/legal
+/overview/llms-full.txt
+/overview/need-help
+/overview/pricinglimits
+/overview/profiles-api/overview
+/overview/self-hosting/clustering
+/overview/self-hosting/docker
+/overview/self-hosting/extensions
+/overview/self-hosting/profiles
+/overview/self-hosting/railway
+/overview/self-hosting/render
+/overview/self-hosting/steel-local-vs-steel-cloud
+/overview/self-hosting/webrtc
+/overview/sessions-api/configuration
+/overview/sessions-api/dedicated-ips
+/overview/sessions-api/embed-sessions
+/overview/sessions-api/embed-sessions/live-sessions
+/overview/sessions-api/embed-sessions/past-sessions
+/overview/sessions-api/fullscreen-mode
+/overview/sessions-api/human-in-the-loop
+/overview/sessions-api/mobile-mode
+/overview/sessions-api/multi-region
+/overview/sessions-api/overview
+/overview/sessions-api/quickstart
+/overview/sessions-api/reusing-auth-context
+/overview/sessions-api/session-lifecycle
+/overview/skills
+/overview/skills/available-skills
+/overview/skills/available-skills/steel-browser
+/overview/skills/available-skills/steel-developer
+/overview/skills/available-skills/steel-reliability
+/overview/skills/available-skills/steel-session-debugging
+/overview/skills/available-skills/steel-skill-creator
+/overview/skills/troubleshooting
+/overview/stealth/captcha-solving
+/overview/stealth/proxies
+/overview/steel-cli
+/overview/stripe-projects
+/steel-js-sdk