Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mani273's avatar

Losing hope with inertia js ssr with react (typescript) and laravel. Hydration mismatch error but technically there should not be any mismatch!

I am unable to figure out why these errors occur. Initially I was faced with the route not defined error. I think I have figured it out to some extent (still not have fixed it) but now I am facing the hydration mismatch error! The problem is this error still pops up if I replace everything with a simple div!

Here is the error:

Browser console error

Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

  <Inertia initialPage={{...}} initialComponent={function AboutUs} resolveComponent={function resolveComponent} ...>
    <AboutUs errors={{}} flash={{status:null}} ziggy={{url:"http:...", ...}} auth={{user:null, ...}}>
      <MainLayout>
        <MainFooter>
+         <div>

Now here is my MainLayout.tsx ( I have commented out code and put the rest under mounted to isolate the issue)

import MainFooter from "@/Components/MainFooter";
import MainNavigation from "@/Components/Navigation/MainNavigation";
import { ReactNode, useEffect, useState } from "react";
import { Toaster } from "sonner";

export default function MainLayout({ children }: { children: ReactNode }) {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);

    return (
        <>
            {mounted && <MainNavigation />}
            {/* <main>{children}</main> */}
            {mounted && <Toaster richColors closeButton expand />}
            <MainFooter />
        </>
    );
}

Now here is my MainFooter.tsx

export default function MainFooter() {
    return <div>dasdsada</div>;
}

So I don't really understand how hydration mismatch can occur here? It should render the same client and server side?!

Here is my app.tsx

import './bootstrap';
import '../css/app.css';
import './echo';

import { createInertiaApp } from '@inertiajs/react'
import { createRoot, hydrateRoot } from 'react-dom/client'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

createInertiaApp({
    title: (title) => `${title}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.tsx`, import.meta.glob('./Pages/**/*.tsx')),
    setup({ el, App, props }) {
        if (props.initialPage.component.includes('ssr')) {
            hydrateRoot(el, <App {...props} />);
        } else {
            createRoot(el).render(<App {...props} />);
        }
    },
    progress: {
        color: '#4B5563',
    },
});

Here is my ssr.tsx

I am truly unable to solve this issue. I have read all the forums on this issue! If anyone can give me valuable insight on how to solve this, it would be really helpful! Unless I put everything under the boolean value of the mounted state variable, I keep getting the hydration mismatch error!

0 likes
5 replies
JussiMannisto's avatar
Level 50

I compared your setup to one of my projects, and noticed at least one thing. Try changing this:

-location: new URL(props.initialPage.props.ziggy.url),
+location: new URL(props.initialPage.props.ziggy.location),

ziggy.url is the host URL, ziggy.location holds the page URL.

JussiMannisto's avatar

Btw, where does initialPage come from? I've just used the page object:

global.route = (name, params, absolute) => route(name, params, absolute, {
	...page.props.ziggy,
	location: new URL(page.props.ziggy.location),
});
mani273's avatar

Thank you. This fixed the hydration mismatch error!!

mani273's avatar

Also I figured out and fixed the route not defined that occurred on a specific page only. If possible can you also explain why this below code caused the route not defined error? I have fixed the error but it would be better if someone explains the cause! Thank you again!

const gettingStartedOptions: {
    title: string;
    href: string;
    description: string;
}[] = [
    {
        title: "How it works",
        href: route("getting-started"), // error
        description: "Start now by posting your tasks that needs to be done",
    },
    {
        title: "For customers",
        href: route("getting-started") + "#for-customers", // error
        description: "Get your work done quickly with minimal effort",
    },
    {
        title: "For contractors",
        href: route("getting-started") + "#for-contractors", // error
        description: "Join us to boost your business and reach more customers",
    },
];

However the route works when using directly inside the component!

<Link
       className="flex gap-5 h-full w-full select-none flex-col justify-end rounded-md bg-linear-to-b from-muted/50 to-muted p-6 no-underline outline-hidden focus:shadow-md"
       href={route("home")}
 >

In the above component, I don't get the route not defined error

JussiMannisto's avatar

Hard to say without seeing the full code. Is the the 'getting-started' route actually defined and given that name? It might also have something to do with your Ziggy configuration or setup.

Please or to participate in this conversation.