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.