Skip to content

Commit 3516b03

Browse files
Ability to use <script> element for initial page data (#2687)
* Switch to dedicated script element with JSON inside * Introduce `future.useScriptElementForInitialPage` config setting * tests * Refactor hard-coded `_page` suffix * Fix styling * Update ssr.spec.ts --------- Co-authored-by: Pascal Baljet <[email protected]>
1 parent 0e6cc7b commit 3516b03

File tree

13 files changed

+140
-21
lines changed

13 files changed

+140
-21
lines changed

packages/core/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const config = new Config<InertiaAppConfig>({
7979
preserveEqualProps: false,
8080
useDataInertiaHeadAttribute: false,
8181
useDialogForErrorModal: false,
82+
useScriptElementForInitialPage: false,
8283
},
8384
prefetch: {
8485
cacheFor: 30_000,

packages/core/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ export type InertiaAppConfig = {
512512
preserveEqualProps: boolean
513513
useDataInertiaHeadAttribute: boolean
514514
useDialogForErrorModal: boolean
515+
useScriptElementForInitialPage: boolean
515516
}
516517
prefetch: {
517518
cacheFor: CacheForOption | CacheForOption[]

packages/react/src/createInertiaApp.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
router,
88
setupProgress,
99
} from '@inertiajs/core'
10-
import { ReactElement, createElement } from 'react'
10+
import { Fragment, ReactElement, createElement } from 'react'
1111
import { renderToString } from 'react-dom/server'
1212
import App, { InertiaAppProps, type InertiaApp } from './App'
1313
import { config } from './index'
@@ -61,8 +61,13 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
6161
config.replace(defaults)
6262

6363
const isServer = typeof window === 'undefined'
64+
const useScriptElementForInitialPage = config.get('future.useScriptElementForInitialPage')
6465
const el = isServer ? null : document.getElementById(id)
65-
const initialPage = page || JSON.parse(el?.dataset.page || '{}')
66+
const elPage =
67+
isServer || !useScriptElementForInitialPage
68+
? null
69+
: document.querySelector(`script[data-page="${id}"][type="application/json"]`)
70+
const initialPage = page || JSON.parse(elPage?.textContent || el?.dataset.page || '{}')
6671

6772
// @ts-expect-error - This can be improved once we remove the 'unknown' type from the resolver...
6873
const resolveComponent = (name) => Promise.resolve(resolve(name)).then((module) => module.default || module)
@@ -104,16 +109,31 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
104109
}
105110

106111
if (isServer && render) {
107-
const body = await render(
108-
createElement(
109-
'div',
110-
{
111-
id,
112-
'data-page': JSON.stringify(initialPage),
113-
},
114-
reactApp as ReactElement,
115-
),
116-
)
112+
const element = () => {
113+
if (!useScriptElementForInitialPage) {
114+
return createElement(
115+
'div',
116+
{
117+
id,
118+
'data-page': JSON.stringify(initialPage),
119+
},
120+
reactApp as ReactElement,
121+
)
122+
}
123+
124+
return createElement(
125+
Fragment,
126+
null,
127+
createElement('script', {
128+
'data-page': id,
129+
type: 'application/json',
130+
dangerouslySetInnerHTML: { __html: JSON.stringify(initialPage) },
131+
}),
132+
createElement('div', { id }, reactApp as ReactElement),
133+
)
134+
}
135+
136+
const body = await render(element())
117137

118138
return { head, body }
119139
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default ({ message }: { message: string }) => (
2+
<div>
3+
<h1 data-testid="ssr-title">SSR Page With Script Element</h1>
4+
<p data-testid="message">{message}</p>
5+
</div>
6+
)

packages/react/test-app/ssr.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ createServer((page) =>
1111
return pages[`./Pages/${name}.tsx`]
1212
},
1313
setup: ({ App, props }) => <App {...props} />,
14+
defaults: {
15+
future: {
16+
useScriptElementForInitialPage: page.component === 'SSR/PageWithScriptElement',
17+
},
18+
},
1419
}),
1520
)

packages/svelte/src/createInertiaApp.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
3939
config.replace(defaults)
4040

4141
const isServer = typeof window === 'undefined'
42+
const useScriptElementForInitialPage = config.get('future.useScriptElementForInitialPage')
4243
const el = isServer ? null : document.getElementById(id)
43-
const initialPage = page || JSON.parse(el?.dataset.page || '{}')
44+
const elPage =
45+
isServer || !useScriptElementForInitialPage
46+
? null
47+
: document.querySelector(`script[data-page="${id}"][type="application/json"]`)
48+
const initialPage = page || JSON.parse(elPage?.textContent || el?.dataset.page || '{}')
4449

4550
const resolveComponent = (name: string) => Promise.resolve(resolve(name))
4651

@@ -59,7 +64,9 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
5964
const { html, head, css } = svelteApp
6065

6166
return {
62-
body: `<div data-server-rendered="true" id="${id}" data-page="${escape(JSON.stringify(initialPage))}">${html}</div>`,
67+
body: useScriptElementForInitialPage
68+
? `<script data-page="${id}" type="application/json">${JSON.stringify(initialPage)}</script><div data-server-rendered="true" id="${id}">${html}</div>`
69+
: `<div data-server-rendered="true" id="${id}" data-page="${escape(JSON.stringify(initialPage))}">${html}</div>`,
6370
head: [head, css ? `<style data-vite-css>${css.code}</style>` : ''],
6471
}
6572
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
export let message: string
3+
</script>
4+
5+
<div>
6+
<h1 data-testid="ssr-title">SSR Page With Script Element</h1>
7+
<p data-testid="message">{message}</p>
8+
</div>

packages/svelte/test-app/ssr.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ createServer((page) =>
1212
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1313
return (App as any).render(props)
1414
},
15+
defaults: {
16+
future: {
17+
useScriptElementForInitialPage: page.component === 'SSR/PageWithScriptElement',
18+
},
19+
},
1520
}),
1621
)

packages/vue3/src/createInertiaApp.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
5959
config.replace(defaults)
6060

6161
const isServer = typeof window === 'undefined'
62+
const useScriptElementForInitialPage = config.get('future.useScriptElementForInitialPage')
6263
const el = isServer ? null : document.getElementById(id)
63-
const initialPage = page || JSON.parse(el?.dataset.page || '{}')
64+
const elPage =
65+
isServer || !useScriptElementForInitialPage
66+
? null
67+
: document.querySelector(`script[data-page="${id}"][type="application/json"]`)
68+
const initialPage = page || JSON.parse(elPage?.textContent || el?.dataset.page || '{}')
6469

6570
const resolveComponent = (name: string) => Promise.resolve(resolve(name)).then((module) => module.default || module)
6671

@@ -103,14 +108,31 @@ export default async function createInertiaApp<SharedProps extends PageProps = P
103108
}
104109

105110
if (isServer && render) {
111+
const element = () => {
112+
if (!useScriptElementForInitialPage) {
113+
return h('div', {
114+
id,
115+
'data-page': JSON.stringify(initialPage),
116+
innerHTML: vueApp ? render(vueApp) : '',
117+
})
118+
}
119+
120+
return [
121+
h('script', {
122+
'data-page': id,
123+
type: 'application/json',
124+
innerHTML: JSON.stringify(initialPage),
125+
}),
126+
h('div', {
127+
id,
128+
innerHTML: vueApp ? render(vueApp) : '',
129+
}),
130+
]
131+
}
132+
106133
const body = await render(
107134
createSSRApp({
108-
render: () =>
109-
h('div', {
110-
id,
111-
'data-page': JSON.stringify(initialPage),
112-
innerHTML: vueApp ? render(vueApp) : '',
113-
}),
135+
render: () => element(),
114136
}),
115137
)
116138

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
message: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<div>
9+
<h1 data-testid="ssr-title">SSR Page With Script Element</h1>
10+
<p data-testid="message">{{ message }}</p>
11+
</div>
12+
</template>

0 commit comments

Comments
 (0)