diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index da1425e2e..6717d9352 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -79,6 +79,7 @@ export const config = new Config({ preserveEqualProps: false, useDataInertiaHeadAttribute: false, useDialogForErrorModal: false, + useScriptElementForInitialPage: false, }, prefetch: { cacheFor: 30_000, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 42e1a62c9..a5baaad48 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -511,6 +511,7 @@ export type InertiaAppConfig = { preserveEqualProps: boolean useDataInertiaHeadAttribute: boolean useDialogForErrorModal: boolean + useScriptElementForInitialPage: boolean } prefetch: { cacheFor: CacheForOption | CacheForOption[] diff --git a/packages/react/src/createInertiaApp.ts b/packages/react/src/createInertiaApp.ts index 7fec18daa..b9ad36c7a 100644 --- a/packages/react/src/createInertiaApp.ts +++ b/packages/react/src/createInertiaApp.ts @@ -7,7 +7,7 @@ import { router, setupProgress, } from '@inertiajs/core' -import { ReactElement, createElement } from 'react' +import { Fragment, ReactElement, createElement } from 'react' import { renderToString } from 'react-dom/server' import App, { InertiaAppProps, type InertiaApp } from './App' import { config } from './index' @@ -61,8 +61,13 @@ export default async function createInertiaApp Promise.resolve(resolve(name)).then((module) => module.default || module) @@ -104,16 +109,31 @@ export default async function createInertiaApp { + if (!useScriptElementForInitialPage) { + return createElement( + 'div', + { + id, + 'data-page': JSON.stringify(initialPage), + }, + reactApp as ReactElement, + ) + } + + return createElement( + Fragment, + null, + createElement('script', { + 'data-page': id, + type: 'application/json', + dangerouslySetInnerHTML: { __html: JSON.stringify(initialPage) }, + }), + createElement('div', { id }, reactApp as ReactElement), + ) + } + + const body = await render(element()) return { head, body } } diff --git a/packages/react/test-app/Pages/SSR/PageWithScriptElement.tsx b/packages/react/test-app/Pages/SSR/PageWithScriptElement.tsx new file mode 100644 index 000000000..d6cb52ab3 --- /dev/null +++ b/packages/react/test-app/Pages/SSR/PageWithScriptElement.tsx @@ -0,0 +1,6 @@ +export default ({ message }: { message: string }) => ( +
+

SSR Page With Script Element

+

{message}

+
+) diff --git a/packages/react/test-app/ssr.tsx b/packages/react/test-app/ssr.tsx index 895050a13..bc16e9e7d 100644 --- a/packages/react/test-app/ssr.tsx +++ b/packages/react/test-app/ssr.tsx @@ -11,5 +11,10 @@ createServer((page) => return pages[`./Pages/${name}.tsx`] }, setup: ({ App, props }) => , + defaults: { + future: { + useScriptElementForInitialPage: page.component === 'SSR/PageWithScriptElement', + }, + }, }), ) diff --git a/packages/svelte/src/createInertiaApp.ts b/packages/svelte/src/createInertiaApp.ts index d57fcff99..363b57940 100644 --- a/packages/svelte/src/createInertiaApp.ts +++ b/packages/svelte/src/createInertiaApp.ts @@ -39,8 +39,13 @@ export default async function createInertiaApp Promise.resolve(resolve(name)) @@ -59,7 +64,9 @@ export default async function createInertiaApp${html}`, + body: useScriptElementForInitialPage + ? `
${html}
` + : `
${html}
`, head: [head, css ? `` : ''], } } diff --git a/packages/svelte/test-app/Pages/SSR/PageWithScriptElement.svelte b/packages/svelte/test-app/Pages/SSR/PageWithScriptElement.svelte new file mode 100644 index 000000000..dc5e7ec57 --- /dev/null +++ b/packages/svelte/test-app/Pages/SSR/PageWithScriptElement.svelte @@ -0,0 +1,8 @@ + + +
+

SSR Page With Script Element

+

{message}

+
diff --git a/packages/svelte/test-app/ssr.ts b/packages/svelte/test-app/ssr.ts index da514823a..edae0d7a7 100644 --- a/packages/svelte/test-app/ssr.ts +++ b/packages/svelte/test-app/ssr.ts @@ -12,5 +12,10 @@ createServer((page) => // eslint-disable-next-line @typescript-eslint/no-explicit-any return (App as any).render(props) }, + defaults: { + future: { + useScriptElementForInitialPage: page.component === 'SSR/PageWithScriptElement', + }, + }, }), ) diff --git a/packages/vue3/src/createInertiaApp.ts b/packages/vue3/src/createInertiaApp.ts index f9f818f8e..9aecd4fd7 100644 --- a/packages/vue3/src/createInertiaApp.ts +++ b/packages/vue3/src/createInertiaApp.ts @@ -59,8 +59,13 @@ export default async function createInertiaApp Promise.resolve(resolve(name)).then((module) => module.default || module) @@ -103,14 +108,31 @@ export default async function createInertiaApp { + if (!useScriptElementForInitialPage) { + return h('div', { + id, + 'data-page': JSON.stringify(initialPage), + innerHTML: vueApp ? render(vueApp) : '', + }) + } + + return [ + h('script', { + 'data-page': id, + type: 'application/json', + innerHTML: JSON.stringify(initialPage), + }), + h('div', { + id, + innerHTML: vueApp ? render(vueApp) : '', + }), + ] + } + const body = await render( createSSRApp({ - render: () => - h('div', { - id, - 'data-page': JSON.stringify(initialPage), - innerHTML: vueApp ? render(vueApp) : '', - }), + render: () => element(), }), ) diff --git a/packages/vue3/test-app/Pages/SSR/PageWithScriptElement.vue b/packages/vue3/test-app/Pages/SSR/PageWithScriptElement.vue new file mode 100644 index 000000000..42cdc5e6e --- /dev/null +++ b/packages/vue3/test-app/Pages/SSR/PageWithScriptElement.vue @@ -0,0 +1,12 @@ + + + diff --git a/packages/vue3/test-app/ssr.ts b/packages/vue3/test-app/ssr.ts index 04ebf29f9..450bb82b3 100644 --- a/packages/vue3/test-app/ssr.ts +++ b/packages/vue3/test-app/ssr.ts @@ -16,5 +16,10 @@ createServer((page) => render: () => h(App, props), }).use(plugin) }, + defaults: { + future: { + useScriptElementForInitialPage: page.component === 'SSR/PageWithScriptElement', + }, + }, }), ) diff --git a/tests/app/server.js b/tests/app/server.js index 5cb4d5340..5f359a78d 100644 --- a/tests/app/server.js +++ b/tests/app/server.js @@ -45,6 +45,15 @@ app.get('/ssr/page2', (req, res) => }), ) +app.get('/ssr/page-with-script-element', (req, res) => + inertia.renderSSR(req, res, { + component: 'SSR/PageWithScriptElement', + props: { + message: 'Hello from script element!', + }, + }), +) + // Intercepts all .js assets (including files loaded via code splitting) app.get(/.*\.js$/, (req, res) => res.sendFile(path.resolve(__dirname, '../../packages/', inertia.package, 'test-app/dist', req.path.substring(1))), diff --git a/tests/ssr.spec.ts b/tests/ssr.spec.ts index 0f3c977fc..a00293534 100644 --- a/tests/ssr.spec.ts +++ b/tests/ssr.spec.ts @@ -32,6 +32,24 @@ test.describe('SSR', () => { }) }) + test('embeds page data in a script element instead of data-page attribute', async ({ page }) => { + const response = await page.request.get('/ssr/page-with-script-element') + const html = await response.text() + + expect(html).toContain('data-page="app"') + expect(html).toContain('