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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion app/(home)/_pages/page.en.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -203,7 +204,7 @@ export default function HomePage() {
icon={<Sparkles />}
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.`}
/>
</Cards>
</section>
Expand Down
3 changes: 2 additions & 1 deletion app/(home)/changelog/page.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -21,5 +22,5 @@ function getLatestChangelogSlug() {

export default function ChangelogPage() {
const latest = getLatestChangelogSlug();
redirect(`/changelog/${latest}`);
redirect(docsPath(`/changelog/${latest}`));
}
3 changes: 2 additions & 1 deletion app/(home)/overview/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { permanentRedirect } from 'next/navigation';
import { docsPath } from '@/lib/docs-path';

export default function OverviewPage() {
permanentRedirect('/');
permanentRedirect(docsPath('/'));
}
5 changes: 3 additions & 2 deletions app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -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'),
},
},
};
Expand Down
22 changes: 13 additions & 9 deletions app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
Expand All @@ -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]))
Expand Down Expand Up @@ -257,8 +259,10 @@ export default async function Page(props: {
)}
</div>
{page.data.llm !== false &&
page.url !== '/cookbook' &&
!page.url.startsWith('/cookbook/topics/') && <LLMShare content={LLMContent} />}
stripDocsPath(page.url) !== '/cookbook' &&
!stripDocsPath(page.url).startsWith('/cookbook/topics/') && (
<LLMShare content={LLMContent} />
)}
</div>
<DocsPageDescription />

Expand Down Expand Up @@ -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;
Expand All @@ -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'],
Expand Down
21 changes: 21 additions & 0 deletions app/api/docs-catalog/route.ts
Original file line number Diff line number Diff line change
@@ -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"',
},
},
);
}
11 changes: 6 additions & 5 deletions app/layout.config.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BaseLayoutProps } from '@/components/layouts/shared';
import { docsPath } from '@/lib/docs-path';
/**
* Shared layout configurations
*
Expand All @@ -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",
Expand Down
9 changes: 6 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand All @@ -22,7 +24,7 @@ export const metadata: Metadata = {
description: DOCS_SITE_DESCRIPTION,
alternates: {
types: {
'text/plain': '/llms.txt',
'text/plain': docsPath('/llms.txt'),
},
},
openGraph: {
Expand Down Expand Up @@ -62,6 +64,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<RootProvider
search={{
enabled: true,
options: { api: docsPath('/api/search') },
}}
theme={{
enabled: false,
Expand Down
4 changes: 2 additions & 2 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import type { Metadata } from 'next';
import { Card, Cards } from '@/components/card';
import { LlmsHintCard } from '@/components/home/llms-hint-card';
import { docsUrl } from '@/lib/docs-path';

export const metadata: Metadata = {
title: 'Page not found',
other: {
'ai-context':
'Steel documentation 404. For an LLM-optimized index of all Steel docs (Sessions API, CAPTCHAs, proxies, SDKs), fetch https://docs.steel.dev/llms.txt',
'ai-context': `Steel documentation 404. For an LLM-optimized index of all Steel docs (Sessions API, CAPTCHAs, proxies, SDKs), fetch ${docsUrl('/llms.txt')}`,
},
};

Expand Down
3 changes: 2 additions & 1 deletion app/og/[...slug]/route.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ImageResponse } from 'next/og';
import type { NextRequest } from 'next/server';
import { stripDocsPath } from '@/lib/docs-path';
import { source } from '@/lib/source';

export const dynamic = 'force-static';
Expand Down Expand Up @@ -146,6 +147,6 @@ export async function GET(_req: NextRequest, props: { params: Promise<{ slug: st

export function generateStaticParams() {
return source.getPages().map((page) => ({
slug: page.url.split('/').filter(Boolean),
slug: stripDocsPath(page.url).split('/').filter(Boolean),
}));
}
15 changes: 8 additions & 7 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
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<MetadataRoute.Sitemap> {
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,
);
return {
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<string>();
Expand Down
Loading
Loading