diff --git a/.gitignore b/.gitignore index 5b4f8f533..37984164d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ scripts/seo/pulse/snapshots/ # Vale downloads third-party packages here; keep only the local Steel style. .github/styles/* !.github/styles/Steel/ +.vercel +.env.local diff --git a/app/(home)/_pages/page.en.tsx b/app/(home)/_pages/page.en.tsx index b154c09d1..418a33a83 100644 --- a/app/(home)/_pages/page.en.tsx +++ b/app/(home)/_pages/page.en.tsx @@ -16,6 +16,7 @@ import { RustIcon, TSIcon, } from '@/components/ui/icon'; +import { docsUrl } from '@/lib/docs-path'; import SteelMark from '@/public/images/steel-mark.png'; export default function HomePage() { @@ -203,7 +204,7 @@ export default function HomePage() { icon={} href="/llms.txt" title="Instructions for AI Agents" - description="Point your agent to docs.steel.dev/llms.txt for a fast start with Steel." + description={`Point your agent to ${docsUrl('/llms.txt').replace('https://', '')} for a fast start with Steel.`} /> diff --git a/app/(home)/changelog/page.tsx b/app/(home)/changelog/page.tsx index 6f15e30bf..c8a915369 100644 --- a/app/(home)/changelog/page.tsx +++ b/app/(home)/changelog/page.tsx @@ -1,6 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { redirect } from 'next/navigation'; +import { docsPath } from '@/lib/docs-path'; // Resolve the highest-numbered changelog entry so /changelog always lands on the latest. function getLatestChangelogSlug() { @@ -21,5 +22,5 @@ function getLatestChangelogSlug() { export default function ChangelogPage() { const latest = getLatestChangelogSlug(); - redirect(`/changelog/${latest}`); + redirect(docsPath(`/changelog/${latest}`)); } diff --git a/app/(home)/overview/page.tsx b/app/(home)/overview/page.tsx index 57da5acc1..0d0f5d519 100644 --- a/app/(home)/overview/page.tsx +++ b/app/(home)/overview/page.tsx @@ -1,5 +1,6 @@ import { permanentRedirect } from 'next/navigation'; +import { docsPath } from '@/lib/docs-path'; export default function OverviewPage() { - permanentRedirect('/'); + permanentRedirect(docsPath('/')); } diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index 674920a18..b34f27268 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -1,13 +1,14 @@ import type { Metadata } from 'next'; import { WebPageJsonLd } from '@/components/page-jsonld'; +import { docsPath } from '@/lib/docs-path'; import { DOCS_SITE_DESCRIPTION } from '@/lib/structured-data'; import EnglishPage from './_pages/page.en'; export const metadata: Metadata = { alternates: { - canonical: '/', + canonical: docsPath('/'), types: { - 'text/plain': '/llms.txt', + 'text/plain': docsPath('/llms.txt'), }, }, }; diff --git a/app/[...slug]/page.tsx b/app/[...slug]/page.tsx index 7eb3f76ad..7e80ed862 100644 --- a/app/[...slug]/page.tsx +++ b/app/[...slug]/page.tsx @@ -27,6 +27,7 @@ import { BreadcrumbJsonLd, TechArticleJsonLd, WebPageJsonLd } from '@/components import { Badge } from '@/components/ui/badge'; import * as customIcons from '@/components/ui/icon'; import { TagFilterSystem } from '@/components/ui/tag-filter-system'; +import { docsPath, stripDocsPath } from '@/lib/docs-path'; import { getGitLastModified } from '@/lib/last-modified'; import { getAllFilterablePages, source } from '@/lib/source'; import { DOCS_URL } from '@/lib/structured-data'; @@ -69,7 +70,7 @@ export default async function Page(props: { const allFilterablePages = getAllFilterablePages(); // Extract section from current page URL for scoped filtering - const currentSection = page.url.split('/').filter(Boolean)[1] || 'general'; + const currentSection = stripDocsPath(page.url).split('/').filter(Boolean)[1] || 'general'; // Helper function to get icon component const getIconComponent = (iconName: string) => { @@ -108,10 +109,11 @@ export default async function Page(props: { // Convert icon names to React components const interactiveLinks = page.data.interactiveLinks?.map((link) => ({ ...link, + href: docsPath(link.href), icon: link.icon ? getIconComponent(link.icon) : undefined, })); - const canonicalPath = page.url.replace(/^\/en(\/|$)/, '/'); + const canonicalPath = stripDocsPath(page.url).replace(/^\/en(\/|$)/, '/'); // BreadcrumbList JSON-LD trail: home + section hub + ancestor folders that // have their own page + the page itself. Fumadocs resets the trail at @@ -120,7 +122,7 @@ export default async function Page(props: { // section index sidebarTitles are generic like "Home"). Folder nodes without // an index page carry no url and are dropped: Google requires `item` on // every ListItem except the last. - const stripEn = (url: string) => url.replace(/^\/en(\/|$)/, '/'); + const stripEn = (url: string) => stripDocsPath(url).replace(/^\/en(\/|$)/, '/'); const sectionSlug = canonicalPath.split('/').filter(Boolean)[0]; const sectionPage = sectionSlug ? (source.getPage([sectionSlug]) ?? source.getPage(['en', sectionSlug])) @@ -257,8 +259,10 @@ export default async function Page(props: { )} {page.data.llm !== false && - page.url !== '/cookbook' && - !page.url.startsWith('/cookbook/topics/') && } + stripDocsPath(page.url) !== '/cookbook' && + !stripDocsPath(page.url).startsWith('/cookbook/topics/') && ( + + )} @@ -363,9 +367,9 @@ export async function generateMetadata(props: { } if (!page) notFound(); - const canonicalPath = page.url.replace(/^\/en(\/|$)/, '/'); + const canonicalPath = stripDocsPath(page.url).replace(/^\/en(\/|$)/, '/'); const ogSlug = slug[0] === 'en' ? slug.slice(1) : slug; - const ogImageUrl = `/og/${ogSlug.join('/')}`; + const ogImageUrl = docsPath(`/og/${ogSlug.join('/')}`); const isChangelogEntry = /^\/changelog\/.+/.test(canonicalPath); const publishedAt = (page.data as { publishedAt?: string }).publishedAt; @@ -376,13 +380,13 @@ export async function generateMetadata(props: { description: page.data.description, authors: [{ name: 'Steel', url: 'https://steel.dev' }], alternates: { - canonical: canonicalPath, + canonical: docsPath(canonicalPath), }, ...(isChangelogEntry && { robots: { index: false, follow: true } }), openGraph: { title: page.data.title, description: page.data.description, - url: canonicalPath, + url: docsPath(canonicalPath), siteName: 'Steel Docs', type: 'article', authors: ['https://steel.dev'], diff --git a/app/api/docs-catalog/route.ts b/app/api/docs-catalog/route.ts new file mode 100644 index 000000000..f5246cdaf --- /dev/null +++ b/app/api/docs-catalog/route.ts @@ -0,0 +1,21 @@ +// ABOUTME: Serves the RFC 9727 API catalog with its URLs on the configured docs origin and prefix. +// ABOUTME: Path mode rewrites /docs/.well-known/api-catalog here; root mode serves the public file. +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { DOCS_ORIGIN, DOCS_PATH_PREFIX } from '@/lib/docs-path'; + +export const dynamic = 'force-static'; + +/** Path-mode version of the existing catalog; keep its root-mode source intact. */ +export async function GET() { + const catalog = await readFile(join(process.cwd(), 'public/.well-known/api-catalog'), 'utf8'); + return new Response( + catalog.replaceAll('https://docs.steel.dev', `${DOCS_ORIGIN}${DOCS_PATH_PREFIX}`), + { + headers: { + 'Content-Type': + 'application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727"', + }, + }, + ); +} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index a93889f1f..07081e6d9 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -1,4 +1,5 @@ import type { BaseLayoutProps } from '@/components/layouts/shared'; +import { docsPath } from '@/lib/docs-path'; /** * Shared layout configurations * @@ -13,27 +14,27 @@ export const baseOptions: BaseLayoutProps = { links: [ { text: 'Overview', - url: '/', + url: docsPath('/'), active: 'url', }, { text: 'Integrations', - url: '/integrations', + url: docsPath('/integrations'), active: 'nested-url', }, { text: 'Cookbook', - url: '/cookbook', + url: docsPath('/cookbook'), active: 'nested-url', }, { text: 'Changelog', - url: '/changelog', + url: docsPath('/changelog'), active: 'nested-url', }, { text: 'API Reference', - url: '/api-reference', + url: docsPath('/api-reference'), }, // { // type: "menu", diff --git a/app/layout.tsx b/app/layout.tsx index 5510ab6fc..8dfa0712f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,13 +7,15 @@ import type { ReactNode } from 'react'; import { SiteIdentityJsonLd } from '@/components/page-jsonld'; import { inter, jetBrainsMono } from '@/fonts'; import { KeyboardShortcutsProvider } from '@/hooks/use-keyboard-shortcuts'; +import { DOCS_ORIGIN, docsPath } from '@/lib/docs-path'; import { DOCS_SITE_DESCRIPTION, DOCS_SITE_NAME } from '@/lib/structured-data'; import { QueryProvider } from '@/providers/query-provider'; -const OG_IMAGE = '/og/overview'; +const OG_IMAGE = docsPath('/og/overview'); export const metadata: Metadata = { - metadataBase: new URL('https://docs.steel.dev'), + metadataBase: new URL(DOCS_ORIGIN), + icons: { icon: docsPath('/favicon.ico') }, applicationName: DOCS_SITE_NAME, title: { template: `%s | ${DOCS_SITE_NAME}`, @@ -22,7 +24,7 @@ export const metadata: Metadata = { description: DOCS_SITE_DESCRIPTION, alternates: { types: { - 'text/plain': '/llms.txt', + 'text/plain': docsPath('/llms.txt'), }, }, openGraph: { @@ -62,6 +64,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { ({ - slug: page.url.split('/').filter(Boolean), + slug: stripDocsPath(page.url).split('/').filter(Boolean), })); } diff --git a/app/sitemap.ts b/app/sitemap.ts index daee590d1..63b6c48ff 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,15 +1,16 @@ import type { MetadataRoute } from 'next'; +import { docsUrl, stripDocsPath } from '@/lib/docs-path'; import { getLastModified } from '@/lib/last-modified'; import { source } from '@/lib/source'; -const SITE_URL = 'https://docs.steel.dev'; - export default async function sitemap(): Promise { - const pages = source.getPages().filter((page) => !/^\/(en\/)?changelog\/.+/.test(page.url)); + const pages = source + .getPages() + .filter((page) => !/^\/(en\/)?changelog\/.+/.test(stripDocsPath(page.url))); const pageEntries = await Promise.all( pages.map(async (page) => { - const url = `${SITE_URL}${page.url.replace(/^\/en(\/|$)/, '/').replace(/\/$/, '/')}`; + const url = docsUrl(stripDocsPath(page.url).replace(/^\/en(\/|$)/, '/')); const lastModified = await getLastModified( (page.data as { _file?: { absolutePath?: string } })._file?.absolutePath, ); @@ -17,19 +18,19 @@ export default async function sitemap(): Promise { url, ...(lastModified ? { lastModified } : {}), changeFrequency: 'weekly' as const, - priority: page.url === '/' ? 1 : 0.8, + priority: stripDocsPath(page.url) === '/' ? 1 : 0.8, }; }), ); const entries: MetadataRoute.Sitemap = [ { - url: `${SITE_URL}/`, + url: docsUrl('/'), changeFrequency: 'weekly', priority: 1, }, ...pageEntries.filter( - ({ url }) => url !== `${SITE_URL}/overview` && url !== `${SITE_URL}/overview/`, + ({ url }) => url !== docsUrl('/overview') && url !== docsUrl('/overview/'), ), ]; const seenUrls = new Set(); diff --git a/bun.lock b/bun.lock index cec7e1e34..53e4fd970 100644 --- a/bun.lock +++ b/bun.lock @@ -29,6 +29,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", @@ -480,7 +481,7 @@ "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="], - "@next/env": ["@next/env@15.5.21", "", {}, "sha512-hjJI/GfrjWHgNguRIBzItjRRu0m3Nrz17GhxsjuHfjIvg9hyg3239REd2dpI+bpMTFuVrVprHzEQ19m++cDtbw=="], + "@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.21", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZfjqPEdi6TRC/fWx7UDbwb1fbVgyh2uD5tVTRKIDZDlYM+UNuE/LafDG2fwuAoZilADpABh46OY/F5qf9JjqLQ=="], @@ -796,6 +797,8 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/md5": ["@types/md5@2.3.6", "", {}, "sha512-WD69gNXtRBnpknfZcb4TRQ0XJQbUPZcai/Qdhmka3sxUR3Et8NrXoeAoknG/LghYHTf4ve795rInVYHBTQdNVA=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], @@ -834,6 +837,8 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "@vercel/microfrontends": ["@vercel/microfrontends@2.4.0", "", { "dependencies": { "@next/env": "16.0.10", "@types/md5": "^2.3.5", "ajv": "^8.17.1", "commander": "^12.1.0", "cookie": "1.0.2", "fast-glob": "^3.3.2", "http-proxy": "^1.18.1", "jsonc-parser": "^3.3.1", "md5": "^2.3.0", "nanoid": "^3.3.9", "path-to-regexp": "6.3.0", "semver": "^7.7.2" }, "peerDependencies": { "@sveltejs/kit": ">=1", "@vercel/analytics": ">=1.5.0", "@vercel/speed-insights": ">=1.2.0", "next": ">=13", "react": ">=17.0.0", "react-dom": ">=17.0.0", "vite": ">=5" }, "optionalPeers": ["@sveltejs/kit", "@vercel/analytics", "@vercel/speed-insights", "next", "react", "react-dom", "vite"], "bin": { "microfrontends": "cli/index.cjs" } }, "sha512-dpUzyjpLtE40gB+vxU3AWy5Dlkx/O91joVc/Q7PTYTtHVmw7ZkKgbR0QFEQJC0kw6SXOb35q46QN1BVwVRL5DQ=="], + "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -908,6 +913,8 @@ "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], @@ -946,10 +953,14 @@ "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="], + "cspell": ["cspell@10.0.0", "", { "dependencies": { "@cspell/cspell-json-reporter": "10.0.0", "@cspell/cspell-performance-monitor": "10.0.0", "@cspell/cspell-pipe": "10.0.0", "@cspell/cspell-types": "10.0.0", "@cspell/cspell-worker": "10.0.0", "@cspell/dynamic-import": "10.0.0", "@cspell/url": "10.0.0", "ansi-regex": "^6.2.2", "chalk": "^5.6.2", "chalk-template": "^1.1.2", "commander": "^14.0.3", "cspell-config-lib": "10.0.0", "cspell-dictionary": "10.0.0", "cspell-gitignore": "10.0.0", "cspell-glob": "10.0.0", "cspell-io": "10.0.0", "cspell-lib": "10.0.0", "fast-json-stable-stringify": "^2.1.0", "flatted": "^3.4.2", "semver": "^7.7.4", "tinyglobby": "^0.2.15" }, "bin": { "cspell": "bin.mjs", "cspell-esm": "bin.mjs" } }, "sha512-R25gsSR1SLlcGyw48fwJwp0PjXrVdl7RDO/Dm5+s4DvC1uQSlyiUxsr/8ZtbyC/MPeUJFQN9B4luqLlSm0WelQ=="], "cspell-config-lib": ["cspell-config-lib@10.0.0", "", { "dependencies": { "@cspell/cspell-types": "10.0.0", "comment-json": "^4.6.2", "smol-toml": "^1.6.1", "yaml": "^2.8.3" } }, "sha512-HWK7SRnJ3N/kOThw/uzmXmQYCzBxu58Jkq2hHyte1voDl118BeNFoaNRWMpYdHbBi3kCj8gaZu8wGtm+Zmdhxw=="], @@ -1168,6 +1179,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="], "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], @@ -1208,6 +1221,8 @@ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + "follow-redirects": ["follow-redirects@1.16.1", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg=="], + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "foreach": ["foreach@2.0.6", "", {}, "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="], @@ -1294,6 +1309,8 @@ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "http-proxy": ["http-proxy@1.18.1", "", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="], + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -1328,6 +1345,8 @@ "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], @@ -1404,6 +1423,8 @@ "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], "katex": ["katex@0.16.25", "", { "dependencies": { "commander": "^8.3.0" }, "bin": "cli.js" }, "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q=="], @@ -1472,6 +1493,8 @@ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="], + "mdast-util-directive": ["mdast-util-directive@3.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], @@ -1662,6 +1685,8 @@ "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1754,6 +1779,8 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -2026,6 +2053,10 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@vercel/microfrontends/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "@vercel/microfrontends/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "coa/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -2068,6 +2099,8 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "next/@next/env": ["@next/env@15.5.21", "", {}, "sha512-hjJI/GfrjWHgNguRIBzItjRRu0m3Nrz17GhxsjuHfjIvg9hyg3239REd2dpI+bpMTFuVrVprHzEQ19m++cDtbw=="], + "next/sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], diff --git a/components/author-profile.tsx b/components/author-profile.tsx index 431adb2db..61310254f 100644 --- a/components/author-profile.tsx +++ b/components/author-profile.tsx @@ -1,8 +1,6 @@ import { Github, Globe } from 'lucide-react'; -import Link from 'next/link'; -import { getAuthorPersonId } from '@/lib/structured-data'; - -const SITE_URL = 'https://docs.steel.dev'; +import { DocsLink as Link } from '@/components/docs-link'; +import { getAuthorPersonId, DOCS_URL as SITE_URL } from '@/lib/structured-data'; interface Props { handle: string; diff --git a/components/banner.tsx b/components/banner.tsx index 84fd204e6..2cd1d3e30 100644 --- a/components/banner.tsx +++ b/components/banner.tsx @@ -1,6 +1,6 @@ import { AlertCircle } from 'lucide-react'; -import Link from 'next/link'; import type React from 'react'; +import { DocsLink as Link } from '@/components/docs-link'; interface BannerProps { message: string; diff --git a/components/breadcrumb-nav.tsx b/components/breadcrumb-nav.tsx index a4987590f..58b11279e 100644 --- a/components/breadcrumb-nav.tsx +++ b/components/breadcrumb-nav.tsx @@ -1,7 +1,7 @@ 'use client'; -import Link from 'next/link'; import { Fragment } from 'react'; +import { DocsLink as Link } from '@/components/docs-link'; import { Breadcrumb, BreadcrumbItem, diff --git a/components/card.tsx b/components/card.tsx index 1fc985a1d..378e7bafb 100644 --- a/components/card.tsx +++ b/components/card.tsx @@ -1,9 +1,10 @@ -import Link, { type LinkProps } from 'fumadocs-core/link'; import { ChevronRight } from 'lucide-react'; import type { HTMLAttributes, ReactNode } from 'react'; +import { DocsFumadocsLink as Link, type LinkProps } from '@/components/docs-link'; import { CopyButton } from '@/components/docskit/copy-button'; import { InteractiveBadge } from '@/components/interactive-badge'; import { Badge } from '@/components/ui/badge'; +import { docsPath } from '@/lib/docs-path'; import { cn } from '@/lib/utils'; export function Cards(props: HTMLAttributes): React.ReactElement { @@ -313,7 +314,7 @@ export function ToolCard({ icon, title, href, className }: ToolCardProps): React if (href) { return ( - + {content} ); diff --git a/components/docs-link.tsx b/components/docs-link.tsx new file mode 100644 index 000000000..3057140c2 --- /dev/null +++ b/components/docs-link.tsx @@ -0,0 +1,29 @@ +// ABOUTME: Next.js and Fumadocs link components that add the docs path prefix to root-relative hrefs. +// ABOUTME: Use them for internal docs links so that path mode keeps navigation under /docs. +import FumadocsLink, { type LinkProps as FumadocsLinkProps } from 'fumadocs-core/link'; +import NextLink from 'next/link'; +import type { ComponentProps } from 'react'; +import { docsPath } from '@/lib/docs-path'; + +/** Keep framework link behavior while applying the docs public URL boundary. */ +export function DocsLink({ href, ...props }: ComponentProps) { + return ( + + ); +} + +export function DocsFumadocsLink({ href, ...props }: ComponentProps) { + 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.