inertia
inertia copied to clipboard
SSR for React; adapter differences between React + Vue
Discussed in https://github.com/inertiajs/inertia/discussions/1081
Originally posted by ZeoKnight February 4, 2022 Having read @aarondfrancis comments here https://github.com/tighten/ziggy/issues/431 and useful blog post https://aaronfrancis.com/2022/using-ziggy-with-inertia-server-side-rendering for dealing with global scope window issues.
How do we approach this in React? the features available within the vue adaptor simply aren't present in the react adaptor?
#Vue adaptor
import {createInertiaApp} from '@inertiajs/inertia-vue3'
createServer((page) => createInertiaApp({
page,
render: renderToString,
resolve: name => require(`./Pages/${name}`),
setup({app, props, plugin}) {
const Ziggy = {
// Pull the Ziggy config off of the props.
...props.initialPage.props.ziggy,
// Build the location, since there is
// no window.location in Node.
location: new URL(props.initialPage.props.ziggy.url)
}
return createSSRApp({
render: () => h(app, props),
}).use(plugin).mixin({
methods: {
route: (name, params, absolute, config = Ziggy) => route(name, params, absolute, config),
},
})
},
}))
#React Adaptor
import {createInertiaApp} from '@inertiajs/inertia-react'
createServer((page) => createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: name => require(`./Pages/${name}`),
setup: ({ App, props }) => <App {...props} />,
}))
Note the setup method does not have plugins or uses a helper method createSSRApp - react setup simply returns the component + props.
Hey I'm glad that post was helpful! Sadly, I know less than nothing about React, otherwise I'd love to help you out here 😂
I am also stuck at this point @ZeoKnight. Were you able to figure it out? Please let me know.
@ZeoKnight @aarondfrancis I am not sure if this is the right approach but this worked for me.
import React, {useState} from 'react'
import ReactDOMServer from 'react-dom/server'
import { createInertiaApp } from '@inertiajs/inertia-react'
import createServer from '@inertiajs/server'
import route from "ziggy-js";
createServer((page) => createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: name => require(`./Pages/${name}`),
setup: ({ App, props }) => {
const Ziggy = {
// Pull the Ziggy config off of the props.
...props.initialPage.props.ziggy,
// Build the location, since there is
// no window.location in Node.
location: new URL(props.initialPage.props.ziggy.url)
}
global.route = (name, params, absolute, config = Ziggy) => route(name, params, absolute, config);
return <App {...props} />
},
}))
@vampiregrey fantastic stuff! didn't know that global functioned like that.
Only problem I have now is I cannot use route() directly in my react files; errors with:
TypeError: Cannot read property 'posts.index' of undefined
for now, I've been able to replace all calls to route() to global.route() which works perfectly... but feels less than ideal.
side note, does anybody else have an issue with ziggy-js package autocomplete in VSCode? it's never able to find the package correctly for me - have to manually import it (other packages are fine)
I've altered my apprach here slightly; the setup method within my ssr.js file now looks like this:
setup: ({ App, props }) => {
global.ziggyConfig = {
...props.initialPage.props.ziggy,
location: new URL(props.initialPage.props.ziggy.url),
};
return <App {...props} />;
},
I've then created a thin "routeProxy" method:
import route from 'ziggy-js';
function routeProxy(name, params, absolute = true) {
return route(name, params, absolute, global.ziggyConfig);
}
export default routeProxy;
which allows me to force the ziggy config from the global value; without having to use global.route everywhere in my app.
I'll update my post to point back to this issue for the React solution. Nice work getting it sorted!
I hope this would help others.
What I've done is generate the ziggy.js file with php artisan ziggy:generate then import the generated js.
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { createInertiaApp } from '@inertiajs/inertia-react'
import createServer from '@inertiajs/server'
import route from "ziggy-js";
// Import generated ziggy.js
import { Ziggy } from '@/ziggy'
createServer((page) => createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: name => require(`./Pages/${name}`),
setup: ({ App, props }) => {
// Set global function route
global.route = (name, params, absolute, config = Ziggy) => route(name, params, absolute, config);
return <App {...props} />
},
}))
To offer another solution: I followed parts of @aarondfrancis his blog post but swapped out the end for this:
createServer(page => {
globalThis.Ziggy = page.props.ziggy;
return createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: name => require(`./Pages/${name}`).default,
setup: ({ App, props }) => <App {...props} />,
});
});
You see, ziggy by default already tries to find a ziggy instance in globalThis (see https://github.com/tighten/ziggy/blob/b3ebdd831a9da9dd9839c611f7aff7d82d1bf159/src/js/Router.js#L17).
This allows me to use the regular route function from ziggy without having to write a wrapper.
Hope it helps someone!
@rdgout Best solution so far for my case! Thank you very much 😄
Currently, I meet another problem if I render a loop with route, it will fail.
Example like let's say I have a basic action:
Route::get('/posts', function () {
$posts = \App\Models\Post::all();
return Inertia::render('Posts', [
'posts' => $posts,
]);
});
Then, The SSR can not render route in the loop
import React from 'react';
import useRoute from '@/Hooks/useRoute';
import useTypedPage from '@/Hooks/useTypedPage';
import type { Post } from '@/types';
export default function Posts() {
const route = useRoute();
const page = useTypedPage<{
posts: Post[];
}>();
const { posts } = page.props;
return (
<div>
{posts.map(post => (
<div key={post.id}>
<div>{post.title}</div>
<div>{route('posts.show', post.id)}</div>
</div>
))}
</div>
);
}
And route().current() is missing.
UPDATE
Sorry for post a wrong issue. Because I use laravel-jetstream-react. .
It's useRoute have skip excute route() if there is no window. After I change source code. It works. Thanks
anyone had this issue before:
createServer(
^
TypeError: createServer is not a function
at file:///Users/hass/Sites/iqdam/medialake/public/build/ssr.js:9929:1
at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:541:24)
at async loadESM (node:internal/process/esm_loader:91:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
here is my ssr.ts
import { createSSRApp, h } from 'vue';
import { createInertiaApp, Head, Link } from '@inertiajs/inertia-vue3';
import { resolvePageComponent } from 'vite-plugin-laravel/inertia';
import { createPinia } from 'pinia';
import AppLayout from '@/Layouts/AppLayout.vue';
import createServer from '@inertiajs/server';
import { renderToString } from '@vue/server-renderer';
createServer((page) =>
createInertiaApp({
page,
render: renderToString,
resolve: async (name) => {
const page = await resolvePageComponent(name, import.meta.globEager('../views/Pages/**/*.vue'));
page.layout = page.layout || AppLayout;
return page;
},
setup({ app, props, plugin }) {
return createSSRApp({ render: () => h(app, props) })
.use(plugin)
.use(createPinia())
.component('Link', Link) // Register Link globally, so we don't have to import in every file
.component('Head', Head) // Register Head globally, so we don't have to import in every file
.mixin({
// @ts-ignore
methods: { route },
computed: {
$log: () => console.log
}
});
}
})
);
Hey! Thanks so much for your interest in Inertia.js and for sharing this issue/suggestion.
In an attempt to get on top of the issues and pull requests on this project I am going through all the older issues and PRs and closing them, as there's a decent chance that they have since been resolved or are simply not relevant any longer. My hope is that with a "clean slate" me and the other project maintainers will be able to better keep on top of issues and PRs moving forward.
Of course there's a chance that this issue is still relevant, and if that's the case feel free to simply submit a new issue. The only thing I ask is that you please include a super minimal reproduction of the issue as a Git repo. This makes it much easier for us to reproduce things on our end and ultimately fix it.
Really not trying to be dismissive here, I just need to find a way to get this project back into a state that I am able to maintain it. Hope that makes sense! ❤️
In my own case, I was getting the error: ReferenceError: route is not defined even after trying out all the suggestions above. This was because in my app, I use the helper route('name') to create all my URLs. What I did to fix it was to add route to the globalThis module, which makes the route helper available for Node.js.
This works for me:
createServer((page) => {
globalThis.route<RouteName> = (name, params, absolute) =>
route(name, params, absolute, {
...page.props.ziggy,
location: new URL(page.props.ziggy.location),
});
return createInertiaApp({
page,
render: ReactDOMServer.renderToString,
title: (title) => `${title} - ${appName}`,
resolve: async (name) => {
const pages = import.meta.glob("./Pages/**/*.tsx");
let page: any = pages[`./Pages/${name}.tsx`];
if (typeof page === "function") {
page = await page();
}
if (typeof page === "undefined") {
throw new Error(`Page not found: ${name}`);
}
if (!page.default.layout) {
const publicPageFolders = ["Public/"];
const isPublicPage = publicPageFolders.some((folder) =>
name.startsWith(folder),
);
if (isPublicPage) {
page.default.layout = (page: any) => (
<GuestLayout children={page} />
);
}
}
return page;
},
setup: ({ App, props }) => {
return <App {...props} />;
},
});
});
Any idea if there will be an official approach to this? I'm using Inertia - React - SSR and Jetstream but can't seem to get any of the suggestions working.