Replies: 9 comments 9 replies
-
I think there's currently no (easy) way to achieve this with Inertia, because of its protocol. The response contains which component should be rendered, so Inertia isn't aware of which component should be rendered before the XHR response is returned. |
Beta Was this translation helpful? Give feedback.
-
Hey @7ammer If you want to stick to Inertia all the way you can use lazy data evaluation to render the component and then fetch additional data with a second partial visit, that replaces the initial one and preserves state , simple Laravel/Vue example: return Inertia::render('Users', [
...
'users' => Inertia::lazy(fn () => User::get()),
]); onMounted(() => {
Inertia.visit('/users', {
preserveState: true,
replace: true,
only: ['users'],
});
}); This will immediately render the Users-component and fetch the users afterwards. |
Beta Was this translation helpful? Give feedback.
-
This is also a crutial "option" for me. There are many downsides to rendering all the templates on the frontend (duplication of logic, required tooling/compilation, etc.). But the main advantage in my opinion is to have an app that feels really quick and snappy. At the moment Inertia is really convenient over a traditional Vue Router setup but it feels much slower. I would then stick to a much simpler setup with Blade views + JS sprinkles. I think the proposed placeholder feature is a good first step. Is the code available somewhere? It could be a good starting point for a PR if you're open to it. |
Beta Was this translation helpful? Give feedback.
-
I think this is where Inertia should go in the future. I started thinking, what's the point of the Single Page App if the pages don't load instantly? Is it really that different experience from the old fashioned, static websites? Slightly. Is it worth all the hassle if we don't get the immediate response anyway? What's the difference if the loading bar is in the body (if we use |
Beta Was this translation helpful? Give feedback.
-
are there any updates on this topic? |
Beta Was this translation helpful? Give feedback.
-
I managed to make somewhat working solution. // put this in your app.js
if (typeof window !== 'undefined') {
const components = {
'/author': 'Author/Index',
'/author/.*': 'Author/Show',
'/narrator': 'Narrator/Index',
'/narrator/.*': 'Narrator/Show',
'/genre': 'Genre/Index',
'/genre/.*': 'Genre/Show',
'/': 'Main/Index',
'/book': 'Book/Index',
'/top': 'Book/Top',
'/book/.*': 'Book/Show',
};
const progressDelay = 50;
router.on('before', (event) => {
const url = new URL(window.location.href);
const path = event.detail.visit.url.pathname;
setTimeout(() => {
if (
event.detail.visit.url.href !== window.location.href
&& event.detail.visit.method === 'get'
&& event.detail.visit.url.origin === url.origin
&& path !== url.pathname
) {
for (const key of Object.keys(components)) {
const regex = new RegExp('^' + key + '$');
if (regex.test(path)) {
router.push({
component: components[key],
url: event.detail.visit.url.href,
clearHistory: false,
props: {},
preserveScroll: event.detail.visit.preserveScroll,
preserveState: event.detail.visit.preserveState,
});
break;
}
}
}
}, progressDelay);
})
} this uses Inertias Client side visits to display new component before the request is even sent to server.
Anyone has any idea how to improve this? |
Beta Was this translation helpful? Give feedback.
-
I Fixed issues in previous solution. gonna leave code here if someone needs it. This is for laravel and svelte(probably can be easily converted to react/vue by changing handling of Php part retrieves components for each route registered for App\Http\Controllers\* , caches this and passes as prop on initial page load // Put this in HandleInertiaRequests.php
public function getInertiaComponent($actionName): ?string
{
// Skip closures
if ($actionName === 'Closure') {
return null;
}
// Parse controller and method
if (! str_contains($actionName, '@')) {
return null;
}
[$controller, $method] = explode('@', $actionName);
// Skip if controller doesn't exist
if (! class_exists($controller)) {
return null;
}
try {
$reflectionMethod = new \ReflectionMethod($controller, $method);
$fileName = $reflectionMethod->getFileName();
$startLine = $reflectionMethod->getStartLine();
$endLine = $reflectionMethod->getEndLine();
if ($fileName && $startLine && $endLine) {
$source = file_get_contents($fileName);
$lines = array_slice(explode("\n", $source), $startLine - 1, $endLine - $startLine + 1);
$methodCode = implode("\n", $lines);
// Look for Inertia::render or inertia in the code
$isInertia = preg_match('/(?:Inertia::render|inertia)\s*\(\s*[\'"]([^\'"]+)[\'"]/', $methodCode, $matches);
if ($isInertia && isset($matches[1])) {
return $matches[1];
}
}
return null;
} catch (\Exception $e) {
return null;
}
}
public function getWebRoutes(): array
{
$routes = Route::getRoutes();
$filteredRoutes = [];
/** @var \Illuminate\Routing\Route $route */
foreach ($routes as $route) {
if (in_array('GET', $route->methods())) {
$actionName = $route->getActionName();
$routeControllerPath = $route->getAction('controller') ?? '';
if (str_contains($routeControllerPath, 'App\Http\Controllers')) {
$inertiaComponent = $this->getInertiaComponent($actionName);
if (! $inertiaComponent) {
continue;
}
$uri = $route->uri();
$regex = '^'.($uri === '/' ? $uri : ('/'.$uri.'\\/?')).'$';
$params = $route->parameterNames();
foreach ($params as $param) {
$regex = str_replace('{'.$param.'}', '.+', $regex);
}
$filteredRoutes[] = [
'uri' => $uri,
'regex' => $regex,
'action' => $actionName,
'parameters' => $params,
'component' => $inertiaComponent,
];
}
}
}
return array_combine(array_column($filteredRoutes, 'regex'), array_column($filteredRoutes, 'component'));
}
public function share(Request $request): array
{
return array_merge(parent::share($request), [
....
// in frontend, page.props.inertiaLoading === true could be used to show loading state
'inertiaLoading' => Inertia::always(false),
// cache routes, because collecting component names with Reflection takes ~5ms
'routes' => fn () => cache()->remember('inertia-routes', now()->addHours(24), fn () => $this->getWebRoutes()),
]);
} Javascript part // Put this in app.js
if (typeof window !== 'undefined') {
(() => {
let routes;
const progressDelay = 80;
let timeout;
let lastPageUpdate = 0;
let lastPageComponent = 0;
let lastPageProps = {};
let loading = false;
router.on('finish', (event) => {
loading = false;
clearTimeout(timeout);
});
router.on('navigate', (event) => {
if (!loading && event.detail.page?.props?.inertiaLoading) {
// If the request was interrupted while empty component was rendered, and user navigates back to that state, reload data.
router.visit(event.detail.page.url, {
preserveScroll: true,
preserveState: false,
});
}
});
page.subscribe((value) => {
// Sometimes rendering of empty components start before request is finished, but ends after it, replacing content with empty props. Not sure how this happens, but code below 'fixes' it by replacing component with real data.
if (
(new Date()).getTime() - lastPageUpdate < progressDelay
&& lastPageComponent === value?.component
&& Object.values(lastPageProps || {}).length
&& value?.props.inertiaLoading
) {
loading = false;
router.replace({
component: lastPageComponent,
props: lastPageProps,
preserveScroll: true,
preserveState: false,
});
}
lastPageUpdate = (new Date()).getTime();
lastPageComponent = value?.component;
lastPageProps = value?.props;
})
router.on('before', (event) => {
routes = routes || get(page).props.routes;
const url = new URL(window.location.href);
const path = event.detail.visit.url.pathname;
if (
routes
&& !event.detail.visit.prefetch
&& !event.detail.visit.async
) {
timeout = setTimeout(async () => {
if (
event.detail.visit.url.href !== decodeURI(window.location.href)
&& event.detail.visit.url.href !== decodeURI(window.location.href).replace('?', '/?')
&& event.detail.visit.method === 'get'
&& event.detail.visit.url.origin === url.origin
) {
for (const key of Object.keys(routes)) {
const regex = new RegExp(key);
if (regex.test(path)) {
const sameComponent = get(page).component === routes[key];
// Starting empty component rendering
loading = true;
router.push({
component: routes[key],
url: event.detail.visit.url.href,
props: (currentProps) => {
return sameComponent
? {...currentProps, inertiaLoading: true}
: {inertiaLoading: true}
},
preserveScroll: sameComponent,
preserveState: sameComponent,
});
return;
}
}
}
}, progressDelay);
}
})
})()
} This solution has only 2 limitations.
// works
return Inertia::render('Book/Index', []);
// also works
return inertia('Book/Index', [])
// won't be bart of 'routes' prop, you may have to add it manually
return Inertia::render($componentName, []); |
Beta Was this translation helpful? Give feedback.
-
In Inertia 2, there is prefetching and Laravel has a Vite prefetch strategy, so this discussion might be moot, except for the first page load. |
Beta Was this translation helpful? Give feedback.
-
It’d be awesome if Inertia let you choose between client-side and server-side routing. I'm probably missing something but I’m not sure why the author chose to handle routing on the server — it’d be nice to keep state on the server and refresh it completely on every route change, but let the client handle routing so we get instant SPA transitions. Inertia could still take care of state and data transport. |
Beta Was this translation helpful? Give feedback.
-
Hello 👋
I'm looking into Inertia and it seems that if you navigate to a page it waits for the data BEFORE rendering the template. While this keeps things nice and simple it would be nice to also have the ability to be able to render the page/template and THEN load the page data with loading states. This is the kind of experience I would expect in a mobile application. Can it be achieved in Inertia?
All the website's templates are (or at least can be) stored in a bundle.js file when the website first loads, right?
Beta Was this translation helpful? Give feedback.
All reactions