mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
fix(studio): rewrite next/head shim on top of React 19 metadata hoisting
The previous implementation portaled children into document.head on the client and returned null during SSR — meaning <Head> contents were absent from the prerendered HTML. React 19 ships native "document metadata" hoisting: <title> / <meta> / <link> / <style> / <script> rendered anywhere in the tree are hoisted to <head> automatically and work for both client and SSR. Studio uses <Head> exclusively for those metadata tags (verified across pages/), so the shim can simply render children and let React do the hoisting. Side effects: - SSR head content (titles etc.) now lands in the prerendered HTML. - Dedup is by element shape rather than Next's explicit key prop; observable output is equivalent for the metadata tags studio uses. - Non-metadata children (arbitrary tags) would render in place rather than in <head>. No such consumers exist today; if one shows up, swap to a portal + SSR collector. Documented in the file header.
This commit is contained in:
1 parent
47b91d66d9
commit
e3db66050a
1 file changed
+26
-3
@@ -1,7 +1,30 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
// Next/Head's job is to inject children into the document `<head>` and
|
||||
// deduplicate them by `key` prop. React 19 ships native "document
|
||||
// metadata" hoisting: any `<title>`, `<meta>`, `<link>`, `<style>`, or
|
||||
// `<script>` rendered anywhere in the tree is hoisted to `<head>`
|
||||
// automatically and works for both client render and SSR. Studio uses
|
||||
// `<Head>` exclusively for those elements (`<title>`, `<meta>`,
|
||||
// `<link>` — see `pages/maintenance.tsx`, `pages/claim-project.tsx`,
|
||||
// etc.), so the shim can be a passthrough that lets React do the
|
||||
// hoisting.
|
||||
//
|
||||
// Trade-offs vs the previous `createPortal(children, document.head)`
|
||||
// approach:
|
||||
// - SSR: the portal returned `null` on the server, so head content
|
||||
// from `<Head>` was never in the prerendered HTML. Native hoisting
|
||||
// emits the metadata in the prerendered output.
|
||||
// - Deduplication: React 19 dedupes `<title>` (last one wins, same
|
||||
// as `document.title`) and merges `<meta>`/`<link>` by their
|
||||
// attributes. Next dedupes by an explicit `key` prop. The
|
||||
// observable output is equivalent for the consumer set we have.
|
||||
// - Non-metadata children: React 19 only hoists the metadata tags
|
||||
// listed above. If a consumer ever renders an arbitrary element
|
||||
// inside `<Head>`, it'll render in place rather than in `<head>`.
|
||||
// We have no such consumers today; if one shows up, swap to a
|
||||
// portal+SSR-collector setup.
|
||||
|
||||
export default function Head({ children }: { children?: ReactNode }) {
|
||||
if (typeof document === 'undefined') return null
|
||||
return createPortal(<>{children}</>, document.head)
|
||||
return <>{children}</>
|
||||
}
|
||||
Reference in new issue
Block a user