improvement(docs): reduce navigation payload and build work (#9285) · simstudioai/sim@5e4339f · GitHub
Skip to content

Commit 5e4339f

Browse files
authored
improvement(docs): reduce navigation payload and build work (#9285)
* improvement(docs): reduce build work and instrument performance * improvement(docs): defer collapsed navigation and refine performance checks * improvement(docs): streamline navigation without visitor telemetry * fix(docs): preserve dates when loading page content * fix(docs): preserve sitemap dates with lazy content
1 parent 71044e8 commit 5e4339f

12 files changed

Lines changed: 274 additions & 33 deletions

File tree

‎.github/workflows/docs-negotiation.yml‎

Lines changed: 4 additions & 0 deletions

‎apps/docs/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ Use the owner and generation commands listed in the editorial guide. Do not hand
2525
## Validate
2626

2727
From the repository root, run the required lint, type, audit, and test gates in the development guidelines. From this directory, `bun run type-check` compiles MDX and checks the docs workspace, and `bun run build` validates the production site. Inspect affected page types on desktop and a narrow viewport, and check internal links and existing anchors after heading changes.
28+
29+
The docs HTTP workflow also checks sidebar navigation, API language switching, and copying Markdown on desktop and mobile layouts. It uploads screenshots and a JSON report with the HTTP regression artifacts. These checks run against the built site in CI; no visitor telemetry is collected. Use Vercel's build logs and usage reports to compare deployment cost and duration.

‎apps/docs/app/[[...slug]]/page.tsx‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { CodeBlock } from '@/components/ui/code-block'
2626
import { Heading } from '@/components/ui/heading'
2727
import { IntegrationIcon } from '@/components/ui/integration-icon'
2828
import { ResponseSection } from '@/components/ui/response-section'
29-
import { getApiSpecContent, getAuthenticatedCodeSamples, openapi } from '@/lib/openapi'
29+
import { getAuthenticatedCodeSamples, openapi } from '@/lib/openapi'
3030
import { simShikiOptions } from '@/lib/shiki-theme'
3131
import { type PageData, source } from '@/lib/source'
3232
import { DOCS_BASE_URL } from '@/lib/urls'
@@ -266,11 +266,6 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
266266

267267
if (isOpenAPI && data.getAPIPageProps) {
268268
const apiProps = data.getAPIPageProps()
269-
const apiPageContent = getApiSpecContent(
270-
data.title,
271-
data.description,
272-
apiProps.operations ?? []
273-
)
274269

275270
return (
276271
<>
@@ -306,7 +301,10 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
306301
>
307302
<PageHeader title={data.title} className='api-page-header'>
308303
<div className='hidden sm:flex'>
309-
<LLMCopyButton content={apiPageContent} />
304+
<LLMCopyButton
305+
key={page.url}
306+
markdownUrl={`/llms.mdx${page.url === '/' ? '' : page.url}`}
307+
/>
310308
</div>
311309
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
312310
</PageHeader>
@@ -318,23 +316,22 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
318316
)
319317
}
320318

321-
const MDX = data.body
322-
const markdownContent = await data.getText('processed')
319+
const { body: MDX, toc, lastModified } = await data.load()
323320

324321
return (
325322
<>
326323
<StructuredData
327324
title={data.title}
328325
description={data.description || ''}
329326
url={`${BASE_URL}${page.url}`}
330-
dateModified={data.lastModified?.toISOString()}
327+
dateModified={lastModified?.toISOString()}
331328
breadcrumb={breadcrumbs}
332329
/>
333330
<DocsPage
334331
className={DOCS_PAGE_CLASS_NAME}
335332
role='main'
336333
tabIndex={-1}
337-
toc={data.toc.filter(
334+
toc={toc.filter(
338335
(item) => isContentHeading(item) && (slug?.[0] !== 'integrations' || item.depth <= 3)
339336
)}
340337
full={data.full}
@@ -359,7 +356,10 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }>
359356
className='max-sm:[&>h1]:basis-0 max-sm:[&_a]:size-10'
360357
>
361358
<div className='hidden sm:flex'>
362-
<LLMCopyButton content={markdownContent} />
359+
<LLMCopyButton
360+
key={page.url}
361+
markdownUrl={`/llms.mdx${page.url === '/' ? '' : page.url}`}
362+
/>
363363
</div>
364364
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
365365
</PageHeader>

‎apps/docs/app/sitemap.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import { DOCS_BASE_URL } from '@/lib/urls'
44

55
export const revalidate = 3600
66

7-
export default function sitemap(): MetadataRoute.Sitemap {
7+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
88
// The docs root redirects to /introduction, which is listed on its own.
9-
return source
10-
.getPages()
11-
.filter((page) => page.url !== '/')
12-
.map((page) => ({
9+
const entries: MetadataRoute.Sitemap = []
10+
for (const page of source.getPages()) {
11+
if (page.url === '/') continue
12+
const data = page.data.type === 'docs' ? await page.data.load() : undefined
13+
entries.push({
1314
url: `${DOCS_BASE_URL}${page.url}`,
14-
lastModified: 'lastModified' in page.data ? page.data.lastModified : undefined,
15-
}))
15+
lastModified: data?.lastModified,
16+
})
17+
}
18+
return entries
1619
}

‎apps/docs/components/docs-layout/sidebar-components.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function SidebarFolder({ item, children }: SidebarFolderProps) {
112112
)}
113113
{hasChildren && (
114114
<div id={contentId} hidden={!open}>
115-
<div className='mt-px ml-4 flex flex-col gap-px'>{children}</div>
115+
{open && <div className='mt-px ml-4 flex flex-col gap-px'>{children}</div>}
116116
</div>
117117
)}
118118
</div>
Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,57 @@
11
'use client'
22

3-
import { Chip } from '@sim/emcn'
3+
import { useState } from 'react'
4+
import { Chip, useCopyToClipboard } from '@sim/emcn'
45
import { Check, Duplicate } from '@sim/emcn/icons'
5-
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
66

7-
export function LLMCopyButton({ content }: { content: string }) {
8-
const [checked, onClick] = useCopyButton(() => navigator.clipboard.writeText(content))
7+
interface LLMCopyButtonProps {
8+
markdownUrl: string
9+
}
10+
11+
export function LLMCopyButton({ markdownUrl }: LLMCopyButtonProps) {
12+
const { copied, copy } = useCopyToClipboard()
13+
const [pending, setPending] = useState(false)
14+
const [failed, setFailed] = useState(false)
15+
const checked = copied && !pending && !failed
16+
17+
async function onClick() {
18+
setPending(true)
19+
setFailed(false)
20+
const prepare = async () => {
21+
const response = await fetch(markdownUrl, {
22+
signal: typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(10000) : undefined,
23+
})
24+
if (!response.ok) throw new Error('Could not load page Markdown')
25+
return response.text()
26+
}
27+
try {
28+
// Promise-backed writes preserve Safari's transient user activation during the fetch.
29+
const success =
30+
typeof ClipboardItem !== 'undefined' && typeof navigator.clipboard.write === 'function'
31+
? await copy({ prepare, fallback: '' })
32+
: await copy(await prepare())
33+
setFailed(!success)
34+
} catch {
35+
setFailed(true)
36+
} finally {
37+
setPending(false)
38+
}
39+
}
940

1041
return (
11-
<Chip
12-
onClick={onClick}
13-
leftIcon={checked ? Check : Duplicate}
14-
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
15-
>
16-
{checked ? 'Copied' : 'Copy page'}
17-
</Chip>
42+
<div className='relative'>
43+
<Chip
44+
onClick={onClick}
45+
disabled={pending}
46+
aria-busy={pending}
47+
leftIcon={checked ? Check : Duplicate}
48+
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
49+
>
50+
{checked ? 'Copied' : pending ? 'Copying…' : 'Copy page'}
51+
</Chip>
52+
<span role='status' className='absolute top-full right-0 whitespace-nowrap text-caption'>
53+
{failed ? 'Could not copy. Try again.' : ''}
54+
</span>
55+
</div>
1856
)
1957
}

‎apps/docs/lib/source.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,5 @@ export type PageData = DocData &
129129
description?: string
130130
full?: boolean
131131
integration?: { type: string; color: string }
132+
load: () => Promise<DocData & { lastModified?: Date }>
132133
}

‎apps/docs/next.config.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const withMDX = createMDX()
66

77
const config: NextConfig = {
88
reactStrictMode: true,
9-
productionBrowserSourceMaps: true,
109
transpilePackages: ['@sim/emcn', '@sim/workflow-renderer'],
1110
images: {
1211
unoptimized: true,

‎apps/docs/package.json‎

Lines changed: 3 additions & 1 deletion

0 commit comments

Comments
 (0)