Skip to content

Commit 23361b8

Browse files
authored
feature(docs): improve logic for asset origin fix (tldraw#8768)
### Problem Docs and marketing are two separate Next apps on Vercel. When marketing rewrites a path (e.g. `tldraw.dev/quick-start`) and returns docs HTML, the browser’s address bar is still the marketing origin, but the HTML referenced `/_next/static/...` as root-relative paths. Those requests went to marketing’s `/_next` (wrong app / wrong hashes) instead of docs’ `/_next`. Setting `ASSET_PREFIX` only in some environments (or only on Production) was easy to get wrong: Preview builds often had no prefix, and Production could pin the wrong hostname (e.g. a `*.vercel.app` alias) while the live site was `tldraw.dev`, which caused 404s, cache skew (old HTML vs new static files), and confusion. ### What we changed `assetPrefix` (and the same base for `assetUrl()` on `public/` paths) now resolves to: 1. `ASSET_PREFIX` when set (explicit override), else 2. `https://${VERCEL_URL}` on Vercel (the current deployment URL), else 3. unset locally → root-relative assets as before. ### Why it helps - Every Vercel build automatically points JS/CSS (and prefixed `public/` URLs) at that deployment’s static host, without maintaining Preview-specific `ASSET_PREFIX` for every branch. - `ASSET_PREFIX` remains an escape hatch when you need a fixed canonical origin (e.g. force the apex in prod). - Docs HTML served through rewrites still loads `/_next` from the docs deployment that produced the page, which removes the “wrong `/_next` host” class of bugs. ### Change type - [ ] `bugfix` - [x] `improvement` - [ ] `feature` - [ ] `api` - [ ] `other`
1 parent 32d04ed commit 23361b8

3 files changed

Lines changed: 77 additions & 14 deletions

File tree

apps/docs/next.config.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@
33
// Configurable domain for rewrites
44
const REWRITE_DOMAIN = 'tldrawdotdev.framer.website'
55

6+
function resolveAssetOrigin() {
7+
const explicit = process.env.ASSET_PREFIX?.trim()
8+
if (explicit) return explicit.replace(/\/$/, '')
9+
10+
const vercelUrl = process.env.VERCEL_URL?.trim()
11+
if (vercelUrl) return `https://${vercelUrl}`
12+
13+
return undefined
14+
}
15+
616
const nextConfig = {
717
reactStrictMode: true,
8-
assetPrefix: process.env.ASSET_PREFIX || undefined,
18+
assetPrefix: resolveAssetOrigin(),
919
experimental: {
1020
scrollRestoration: true,
1121
},

apps/docs/utils/asset-url.test.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,96 @@ import { assetUrl } from './asset-url'
33

44
describe('assetUrl', () => {
55
it('returns the original path when ASSET_PREFIX is unset', () => {
6-
const prev = process.env.ASSET_PREFIX
6+
const prevPrefix = process.env.ASSET_PREFIX
7+
const prevVercel = process.env.VERCEL_URL
78
try {
89
delete process.env.ASSET_PREFIX
10+
delete process.env.VERCEL_URL
911
expect(assetUrl('/favicon.svg')).toBe('/favicon.svg')
1012
} finally {
11-
process.env.ASSET_PREFIX = prev
13+
process.env.ASSET_PREFIX = prevPrefix
14+
process.env.VERCEL_URL = prevVercel
1215
}
1316
})
1417

1518
it('returns the original path when ASSET_PREFIX is empty/whitespace', () => {
16-
const prev = process.env.ASSET_PREFIX
19+
const prevPrefix = process.env.ASSET_PREFIX
20+
const prevVercel = process.env.VERCEL_URL
1721
try {
1822
process.env.ASSET_PREFIX = ' '
23+
delete process.env.VERCEL_URL
1924
expect(assetUrl('/favicon.svg')).toBe('/favicon.svg')
2025
} finally {
21-
process.env.ASSET_PREFIX = prev
26+
process.env.ASSET_PREFIX = prevPrefix
27+
process.env.VERCEL_URL = prevVercel
2228
}
2329
})
2430

2531
it('prefixes root-relative paths', () => {
26-
const prev = process.env.ASSET_PREFIX
32+
const prevPrefix = process.env.ASSET_PREFIX
33+
const prevVercel = process.env.VERCEL_URL
2734
try {
2835
process.env.ASSET_PREFIX = 'https://docs.example.com'
36+
delete process.env.VERCEL_URL
2937
expect(assetUrl('/images/foo.png')).toBe('https://docs.example.com/images/foo.png')
3038
} finally {
31-
process.env.ASSET_PREFIX = prev
39+
process.env.ASSET_PREFIX = prevPrefix
40+
process.env.VERCEL_URL = prevVercel
3241
}
3342
})
3443

3544
it('strips a trailing slash from the prefix', () => {
36-
const prev = process.env.ASSET_PREFIX
45+
const prevPrefix = process.env.ASSET_PREFIX
46+
const prevVercel = process.env.VERCEL_URL
3747
try {
3848
process.env.ASSET_PREFIX = 'https://docs.example.com/'
49+
delete process.env.VERCEL_URL
3950
expect(assetUrl('/images/foo.png')).toBe('https://docs.example.com/images/foo.png')
4051
} finally {
41-
process.env.ASSET_PREFIX = prev
52+
process.env.ASSET_PREFIX = prevPrefix
53+
process.env.VERCEL_URL = prevVercel
54+
}
55+
})
56+
57+
it('uses VERCEL_URL when ASSET_PREFIX is unset', () => {
58+
const prevPrefix = process.env.ASSET_PREFIX
59+
const prevVercel = process.env.VERCEL_URL
60+
try {
61+
delete process.env.ASSET_PREFIX
62+
process.env.VERCEL_URL = 'tldraw-docs-abc123.vercel.app'
63+
expect(assetUrl('/images/foo.png')).toBe(
64+
'https://tldraw-docs-abc123.vercel.app/images/foo.png'
65+
)
66+
} finally {
67+
process.env.ASSET_PREFIX = prevPrefix
68+
process.env.VERCEL_URL = prevVercel
69+
}
70+
})
71+
72+
it('prefers ASSET_PREFIX over VERCEL_URL', () => {
73+
const prevPrefix = process.env.ASSET_PREFIX
74+
const prevVercel = process.env.VERCEL_URL
75+
try {
76+
process.env.ASSET_PREFIX = 'https://explicit.example.com'
77+
process.env.VERCEL_URL = 'tldraw-docs-abc123.vercel.app'
78+
expect(assetUrl('/favicon.svg')).toBe('https://explicit.example.com/favicon.svg')
79+
} finally {
80+
process.env.ASSET_PREFIX = prevPrefix
81+
process.env.VERCEL_URL = prevVercel
4282
}
4383
})
4484

4585
it('does not change non-root-relative paths', () => {
46-
const prev = process.env.ASSET_PREFIX
86+
const prevPrefix = process.env.ASSET_PREFIX
87+
const prevVercel = process.env.VERCEL_URL
4788
try {
4889
process.env.ASSET_PREFIX = 'https://docs.example.com'
90+
delete process.env.VERCEL_URL
4991
expect(assetUrl('https://cdn.example.com/a.png')).toBe('https://cdn.example.com/a.png')
5092
expect(assetUrl('relative.png')).toBe('relative.png')
5193
} finally {
52-
process.env.ASSET_PREFIX = prev
94+
process.env.ASSET_PREFIX = prevPrefix
95+
process.env.VERCEL_URL = prevVercel
5396
}
5497
})
5598
})

apps/docs/utils/asset-url.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
function resolveAssetOrigin(): string | undefined {
2+
const explicit = process.env.ASSET_PREFIX?.trim()
3+
if (explicit) return explicit.replace(/\/$/, '')
4+
5+
const vercelUrl = process.env.VERCEL_URL?.trim()
6+
if (vercelUrl) return `https://${vercelUrl}`
7+
8+
return undefined
9+
}
10+
111
export function assetUrl(path: string): string {
212
if (!path.startsWith('/')) return path
3-
const prefix = process.env.ASSET_PREFIX?.trim()
4-
if (!prefix) return path
5-
return `${prefix.replace(/\/$/, '')}${path}`
13+
const origin = resolveAssetOrigin()
14+
if (!origin) return path
15+
return `${origin}${path}`
616
}

0 commit comments

Comments
 (0)