Skip to content

Commit fc60921

Browse files
committed
refactor(og): bake getRequest()-based origin into ogImageUrl via createIsomorphicFn
Previous attempt put the iso fn in src/utils/og.ts and added that path to the import-protection denylist files. That misread the config — `files` is a denylist, not an allowlist — so og.ts ended up flagged as a server-only module and CI failed: every route that imports ogImageUrl ("/$libraryId/route.tsx" etc.) tripped "Import denied in client environment". Fix: drop the og.ts entry from the protection config and rely on the start compiler's recognition of `createIsomorphicFn().server(...)` as a client-safe boundary. The `getRequest` import is referenced only inside `.server()`, so import-protection lets it through and the bundler tree-shakes the import out of the client output. Verified locally with `pnpm build` (clean) and the dev server's og:image meta tag rendering as `http://localhost:3000/api/og/...` (request origin) instead of the hardcoded production URL.
1 parent 30bc766 commit fc60921

2 files changed

Lines changed: 28 additions & 33 deletions

File tree

src/utils/og.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { createIsomorphicFn } from '@tanstack/react-start'
12
import { getRequest } from '@tanstack/react-start/server'
23
import type { LibraryId } from '~/libraries'
3-
import { canonicalUrl } from './seo'
44
import {
55
MAX_OG_DESCRIPTION_LENGTH,
66
MAX_OG_TITLE_LENGTH,
@@ -17,30 +17,34 @@ type OgImageOptions = {
1717
/**
1818
* Absolute origin to use for og:image URLs.
1919
*
20-
* Unlike canonical links (which must always point to production),
21-
* og:image URLs MUST be reachable on the same deploy that emitted them
22-
* — social-card validators fetch the URL from the meta tag verbatim.
20+
* Unlike canonical links (which always point to production), og:image
21+
* URLs MUST be reachable on the same deploy that emitted them — social-
22+
* card validators fetch the URL from the meta tag verbatim, so on a
23+
* Netlify deploy preview the og:image must point at the preview origin,
24+
* not at production.
2325
*
24-
* The incoming request URL is the source of truth: on a Netlify deploy
25-
* preview the request hits `deploy-preview-N--tanstack.netlify.app`, so
26-
* the og:image must point there too. `process.env.DEPLOY_PRIME_URL` and
27-
* friends turn out to be unreliable inside the bundled SSR function, so
28-
* we read the origin from the live request instead.
26+
* The incoming request URL is the source of truth. `process.env.URL` /
27+
* `DEPLOY_PRIME_URL` etc. turned out to be unreliable inside our bundled
28+
* SSR function, so read the origin from the live request via TanStack
29+
* Start's `getRequest()`. The server import is referenced only inside
30+
* `.server()`, which the start compiler treats as a client-safe boundary
31+
* — the import is tree-shaken from the client bundle.
2932
*/
30-
function getOgOrigin(): string {
31-
if (!import.meta.env.SSR) return DEFAULT_SITE_URL
32-
try {
33-
const request = getRequest()
34-
if (request?.url) return new URL(request.url).origin
35-
} catch {
36-
// getRequest() throws if called outside an SSR request context
37-
// (e.g. build-time prerender). Fall through to the env-var fallback.
38-
}
39-
const env = process.env
40-
const origin =
41-
env.DEPLOY_PRIME_URL || env.DEPLOY_URL || env.URL || env.SITE_URL
42-
return (origin ?? DEFAULT_SITE_URL).replace(/\/$/, '')
43-
}
33+
const getOgOrigin = createIsomorphicFn()
34+
.server((): string => {
35+
try {
36+
const request = getRequest()
37+
if (request?.url) return new URL(request.url).origin
38+
} catch {
39+
// getRequest() throws if called outside an SSR request context.
40+
}
41+
return DEFAULT_SITE_URL
42+
})
43+
.client((): string =>
44+
typeof window !== 'undefined'
45+
? window.location.origin
46+
: DEFAULT_SITE_URL,
47+
)
4448

4549
/**
4650
* Absolute URL for a package-themed OG image.
@@ -67,9 +71,5 @@ export function ogImageUrl(
6771
const qs = params.toString()
6872
const path = `/api/og/${libraryId}.png${qs ? `?${qs}` : ''}`
6973

70-
// On client (which can't happen in head() but guards against misuse),
71-
// fall through to canonicalUrl which uses the production hostname.
72-
if (!import.meta.env.SSR) return canonicalUrl(path)
73-
7474
return `${getOgOrigin()}${path}`
7575
}

vite.config.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,7 @@ export default defineConfig({
226226
importProtection: {
227227
behavior: 'error',
228228
client: {
229-
// src/utils/og.ts imports getRequest from @tanstack/react-start/server
230-
// to derive the og:image origin from the live request — uses are
231-
// gated by `import.meta.env.SSR`, so Vite tree-shakes the import out
232-
// of the client bundle. Allowlist the file so the static import
233-
// doesn't trip the protection check during bundling.
234-
files: ['**/*.server.*', '**/server/**', '**/utils/og.ts'],
229+
files: ['**/*.server.*', '**/server/**'],
235230
specifiers: [
236231
'@tanstack/react-start/server',
237232
'uploadthing/server',

0 commit comments

Comments
 (0)