Skip to content

Commit 4fe254f

Browse files
committed
feat: move markdown and code rendering to RSCs
1 parent 33a84b6 commit 4fe254f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2795
-2071
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,16 @@
5454
"@tanstack/react-hotkeys": "^0.9.1",
5555
"@tanstack/react-pacer": "^0.21.1",
5656
"@tanstack/react-query": "^5.96.2",
57-
"@tanstack/react-router": "1.168.10",
58-
"@tanstack/react-router-devtools": "1.166.11",
59-
"@tanstack/react-router-ssr-query": "1.166.10",
60-
"@tanstack/react-start": "1.167.16",
57+
"@tanstack/react-router": "1.168.17",
58+
"@tanstack/react-router-devtools": "1.166.13",
59+
"@tanstack/react-router-ssr-query": "1.166.11",
60+
"@tanstack/react-start": "1.167.30",
6161
"@tanstack/react-table": "^8.21.3",
6262
"@types/d3": "^7.4.3",
6363
"@uploadthing/react": "^7.3.3",
6464
"@visx/hierarchy": "^3.12.0",
6565
"@vitejs/plugin-react": "^6.0.1",
66+
"@vitejs/plugin-rsc": "^0.5.23",
6667
"@webcontainer/api": "^1.6.1",
6768
"@xstate/react": "^6.1.0",
6869
"algoliasearch": "^5.50.0",
@@ -75,7 +76,6 @@
7576
"gray-matter": "^4.0.3",
7677
"hast-util-is-element": "^3.0.0",
7778
"hast-util-to-string": "^3.0.1",
78-
"html-react-parser": "^5.2.17",
7979
"jszip": "^3.10.1",
8080
"lru-cache": "^11.2.7",
8181
"lucide-react": "^1.7.0",
@@ -92,8 +92,8 @@
9292
"rehype-callouts": "^2.1.2",
9393
"rehype-parse": "^9.0.1",
9494
"rehype-raw": "^7.0.0",
95+
"rehype-react": "^8.0.0",
9596
"rehype-slug": "^6.0.0",
96-
"rehype-stringify": "^10.0.1",
9797
"remark-gfm": "^4.0.1",
9898
"remark-parse": "^11.0.0",
9999
"remark-rehype": "^11.1.2",

pnpm-lock.yaml

Lines changed: 349 additions & 148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rsc-migration-report.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# RSC Migration Report
2+
3+
Status: pre-ship bundle + code audit complete. Current production Lighthouse baseline captured. Post-deploy production compare still pending.
4+
5+
## Scope
6+
7+
This report tracks the impact of moving markdown-heavy and code-heavy surfaces to React Server Components and server-rendered code highlighting.
8+
9+
Pages explicitly covered:
10+
11+
- `/blog/react-server-components`
12+
- `/router/latest/docs/overview`
13+
- `/router/latest/docs/framework/react/examples/basic`
14+
- `/query/latest`
15+
- `/router/latest`
16+
- `/form/latest`
17+
- `/table/latest`
18+
- `/virtual/latest`
19+
20+
## Methodology
21+
22+
Bundle analysis:
23+
24+
- Baseline: clean `HEAD` worktree at `/tmp/tanstack-bundle-baseline`
25+
- Current: this branch/worktree
26+
- Metric: transitive built client JS graph per representative route from production build assets
27+
- Unit: bytes and gzip bytes
28+
29+
Production baseline metrics:
30+
31+
- Captured against live `https://tanstack.com`
32+
- Tool: Lighthouse via `npx lighthouse`
33+
- Category: `performance`
34+
- Strategy: default Lighthouse mobile simulation
35+
36+
Notes:
37+
38+
- Bundle numbers are the most trustworthy pre-ship metric here
39+
- Lighthouse should be compared on the same production domain before and after deploy
40+
- Production Lighthouse is noisy; treat single-run numbers as directional until we rerun after deploy
41+
42+
## Bundle Impact
43+
44+
Gzipped client JS, before vs after.
45+
46+
| Page | Before | After | Delta |
47+
|---|---:|---:|---:|
48+
| `/blog/react-server-components` | 547,196 B | 394,207 B | -152,989 B |
49+
| `/router/latest/docs/overview` | 563,295 B | 410,644 B | -152,651 B |
50+
| `/router/latest/docs/framework/react/examples/basic` | 421,421 B | 381,079 B | -40,342 B |
51+
| `/table/latest` | 451,744 B | 409,455 B | -42,289 B |
52+
| `/query/latest` | 405,185 B | 412,671 B | +7,486 B |
53+
| `/router/latest` | 403,281 B | 411,259 B | +7,978 B |
54+
| `/form/latest` | 401,951 B | 409,389 B | +7,438 B |
55+
| `/virtual/latest` | 401,535 B | 409,044 B | +7,509 B |
56+
57+
### Bundle Takeaways
58+
59+
- Big wins on markdown-heavy pages:
60+
- blog: about `-153 KB gz`
61+
- docs page: about `-153 KB gz`
62+
- Clear win on docs example page: about `-40 KB gz`
63+
- Clear win on table landing: about `-42 KB gz`
64+
- Slight regressions on most other landing pages: about `+7-8 KB gz`
65+
66+
Interpretation:
67+
68+
- The pages dominated by markdown parsing and client-side highlighting improved materially
69+
- Most landing pages are now architecturally cleaner, but not smaller yet
70+
- The likely reason for the small landing regressions is that the old landing code-example path had more lazy/client indirection, while the new server-rendered example shell is part of the initial experience
71+
72+
## Client Boundary Audit
73+
74+
Verified absent from current `dist/client/assets`:
75+
76+
- `shiki`
77+
- `@shikijs/*`
78+
- `createHighlighter`
79+
- `codeToHtml`
80+
- `html-react-parser`
81+
- `remark-*`
82+
- `rehype-*`
83+
- `rehype-react`
84+
- old client markdown renderer files
85+
- old landing example card files
86+
- representative raw example-source snippets
87+
88+
Implications:
89+
90+
- Markdown rendering pipeline is out of the client build
91+
- Syntax highlighting pipeline is out of the client build
92+
- Landing example source strings are out of the client build
93+
94+
Runtime spot checks also confirmed:
95+
96+
- docs/example pages no longer request Shiki/highlighting assets
97+
- landing pages no longer hit the old client `CodeBlock` path
98+
99+
Nuance:
100+
101+
- Client sourcemaps still reference some server-fn stub names like `fetchRenderedCodeFile` and `fetchLandingCodeExample`
102+
- Those are stubs, not the rendering/highlighting pipeline itself
103+
104+
## Code Simplicity
105+
106+
Git diff summary:
107+
108+
- `46 files changed`
109+
- `1,017 insertions`
110+
- `2,038 deletions`
111+
- Net: about `-1,021` lines
112+
113+
Deleted legacy client-heavy files:
114+
115+
- `src/components/CodeExampleCard.tsx`
116+
- `src/components/LazyCodeExampleCard.tsx`
117+
- `src/components/SimpleMarkdown.tsx`
118+
- `src/components/markdown/Markdown.tsx`
119+
- `src/components/markdown/MarkdownFrameworkHandler.tsx`
120+
- `src/components/markdown/MarkdownTabsHandler.tsx`
121+
- `src/utils/markdown/processor.ts`
122+
123+
New server-focused structure:
124+
125+
- `src/components/markdown/renderCodeBlock.server.tsx`
126+
- `src/components/markdown/CodeBlock.server.tsx`
127+
- `src/utils/markdown/processor.rsc.tsx`
128+
- `src/utils/markdown/renderRsc.tsx`
129+
- `src/components/landing/codeExamples.server.tsx`
130+
- `src/components/landing/LandingCodeExampleCard.server.tsx`
131+
132+
Dependency cleanup:
133+
134+
- Removed `html-react-parser`
135+
- Removed `rehype-stringify`
136+
137+
Architectural simplifications:
138+
139+
- One server markdown pipeline instead of mixed client/server rendering
140+
- One server code-highlighting pipeline instead of client Shiki
141+
- One server landing-example registry instead of repeating large inline code maps across landing pages
142+
- Example pages now use URL-driven server-rendered code panes instead of client raw source + client highlighting
143+
144+
## Current Production Lighthouse Baseline
145+
146+
Captured before shipping these changes.
147+
148+
| Page | Score | FCP | LCP | Speed Index | TBT | CLS | Interactive | Bytes | Requests |
149+
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
150+
| `/query/latest` | 80 | 2.8s | 3.4s | 3.4s | 300ms | 0.001 | 6.1s | 765 KiB | 89 |
151+
| `/blog/react-server-components` | 52 | 3.3s | 3.7s | 3.6s | 1,200ms | 0.15 | 7.8s | 1,101 KiB | 60 |
152+
| `/router/latest/docs/overview` | 78 | 3.0s | 3.6s | 3.9s | 280ms | 0.002 | 7.5s | 917 KiB | 81 |
153+
154+
Notes:
155+
156+
- These are single-run live production baselines, so expect some noise
157+
- Blog is the most likely page to show strong post-deploy Lighthouse improvement because it had the largest client-bundle reduction
158+
- Docs should also improve meaningfully
159+
- Landing-page Lighthouse changes may be neutral or mixed except for table, based on current bundle data
160+
161+
## Post-Deploy Compare
162+
163+
Fill this in after deploy to `main` and re-run Lighthouse on the same production URLs.
164+
165+
| Page | Before Score | After Score | Before LCP | After LCP | Before TBT | After TBT | Before Bytes | After Bytes | Notes |
166+
|---|---:|---:|---:|---:|---:|---:|---:|---:|---|
167+
| `/query/latest` | 80 | | 3.4s | | 300ms | | 765 KiB | | |
168+
| `/blog/react-server-components` | 52 | | 3.7s | | 1,200ms | | 1,101 KiB | | |
169+
| `/router/latest/docs/overview` | 78 | | 3.6s | | 280ms | | 917 KiB | | |
170+
171+
## Conclusions So Far
172+
173+
- Strong technical wins on markdown-heavy and code-heavy content pages
174+
- Clear reduction in client responsibility and code complexity
175+
- Clear removal of markdown parsing and syntax highlighting from the client bundle
176+
- Not a universal bundle win across all landing pages yet
177+
- Best pages to watch post-deploy: blog and docs

src/components/Breadcrumbs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Link } from '@tanstack/react-router'
22
import { ChevronDown } from 'lucide-react'
33
import { twMerge } from 'tailwind-merge'
4-
import type { MarkdownHeading } from '~/utils/markdown/processor'
4+
import type { MarkdownHeading } from '~/utils/markdown/processor.rsc'
55
import {
66
Dropdown,
77
DropdownTrigger,

src/components/CodeExampleCard.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/components/CodeExplorer.tsx

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,15 @@
11
import React from 'react'
2-
import { CodeBlock } from '~/components/markdown'
32
import { FileExplorer } from './FileExplorer'
43
import { InteractiveSandbox } from './InteractiveSandbox'
54
import { CodeExplorerTopBar } from './CodeExplorerTopBar'
65
import type { GitHubFileNode } from '~/utils/documents.server'
76
import type { Library } from '~/libraries'
87
import { twMerge } from 'tailwind-merge'
98

10-
function overrideExtension(ext: string | undefined) {
11-
if (!ext) return 'txt'
12-
13-
// Override some extensions
14-
if (['cts', 'mts'].includes(ext)) return 'ts'
15-
if (['cjs', 'mjs'].includes(ext)) return 'js'
16-
if (['prettierrc', 'babelrc', 'webmanifest'].includes(ext)) return 'json'
17-
if (['env', 'example'].includes(ext)) return 'sh'
18-
if (
19-
[
20-
'gitignore',
21-
'prettierignore',
22-
'log',
23-
'gitattributes',
24-
'editorconfig',
25-
'lock',
26-
'opts',
27-
'Dockerfile',
28-
'dockerignore',
29-
'npmrc',
30-
'nvmrc',
31-
].includes(ext)
32-
)
33-
return 'txt'
34-
35-
return ext
36-
}
37-
389
interface CodeExplorerProps {
3910
activeTab: 'code' | 'sandbox'
4011
codeSandboxUrl: string
41-
currentCode: string
12+
currentCodeRsc: React.ReactNode
4213
currentPath: string
4314
examplePath: string
4415
githubContents: GitHubFileNode[] | undefined
@@ -52,7 +23,7 @@ interface CodeExplorerProps {
5223
export function CodeExplorer({
5324
activeTab,
5425
codeSandboxUrl,
55-
currentCode,
26+
currentCodeRsc,
5627
currentPath,
5728
examplePath,
5829
githubContents,
@@ -116,22 +87,13 @@ export function CodeExplorer({
11687
prefetchFileContent={prefetchFileContent}
11788
setCurrentPath={setCurrentPath}
11889
/>
119-
<div className="flex-1 overflow-auto relative">
120-
<CodeBlock
121-
isEmbedded
122-
className={twMerge(
123-
'h-full border-0',
124-
isFullScreen ? 'max-h-[90dvh]' : 'max-h-[80dvh]',
125-
)}
126-
>
127-
<code
128-
className={`language-${overrideExtension(
129-
currentPath.split('.').pop(),
130-
)}`}
131-
>
132-
{currentCode}
133-
</code>
134-
</CodeBlock>
90+
<div
91+
className={twMerge(
92+
'flex-1 overflow-auto relative',
93+
isFullScreen ? 'max-h-[90dvh]' : 'max-h-[80dvh]',
94+
)}
95+
>
96+
{currentCodeRsc}
13597
</div>
13698
</div>
13799
<InteractiveSandbox

src/components/CopyPageDropdown.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ export function CopyPageDropdown({
116116
// For docs pages in this repo, prefer the site's markdown endpoint so requests stay behind site caching.
117117
const pageMarkdownUrl = (() => {
118118
const base = `${typeof window !== 'undefined' ? window.location.origin : ''}${typeof window !== 'undefined' ? window.location.pathname.replace(/\/$/, '') : ''}.md`
119-
const params = new URLSearchParams()
119+
const params =
120+
typeof window !== 'undefined'
121+
? new URLSearchParams(window.location.search)
122+
: new URLSearchParams()
120123
if (currentFramework) {
121124
params.set('framework', currentFramework)
122125
}

0 commit comments

Comments
 (0)