laravel / jetstream

Tailwind scaffolding for the Laravel framework.
https://jetstream.laravel.com
MIT License
3.97k stars 822 forks source link

SSR Client Side Hydration Issue on new Install #1320

Closed CamKem closed 1 year ago

CamKem commented 1 year ago

Jetstream Version

3.2.2

Jetstream Stack

Inertia

Laravel Version

10.13.2

PHP Version

8.2.1

Database Driver & Version

MySQL

Description

Client side hydration is not working as expected on a fresh install of Laravel & Jetstream. With just the app installed, setup ready to go & no edits (except createSSRApp and not createApp as per the Inertia docs), when accessing /dashboard on first visit, it causes a Hydration node mismatch & the entire AppLayout component renders then disappears leaving just the slot component rendered. When I then look at the elements tab in devtools, there is just a couple of empty divs with display:none set on them & not representative of what rendered at all. There is no errors displayed in the terminal running the SSR server.

Steps To Reproduce

  1. In terminal: Laravel new app
  2. In terminal: php artisan jetstream:install inertia --ssr
  3. In terminal: php artisan migrate
  4. edit app.js to change createApp to createSSRApp
  5. In terminal: npm run build
  6. In terminal: php artisan inertia:start-ssr
  7. In terminal: npm run dev
  8. In browser: Visit /register and create an account & be redirected to /dashboard
  9. In browser: Refresh page & the console displays the hydration node missmatch errors & AppLayout.vue dissapears. Screenshot at Jun 07 20-16-25
driesvints commented 1 year ago

I think you misread. You don't need to touch app.js but create a new ssr.js file. https://inertiajs.com/server-side-rendering#add-server-entry-point

The --ssr flag also already adds all of this for you: https://jetstream.laravel.com/3.x/installation.html#or-install-jetstream-with-inertia

CamKem commented 1 year ago

@driesvints This is a valid issue, I was referring to Client Side Hydration, which instructs as stated in my issue, to change the the app.js to createSSRApp instead of createApp, so rather than being re-rendered it hydrates the static html so it is reactive/etc.

https://inertiajs.com/server-side-rendering#client-side-hydration

Since your website is now being server-side rendered, you can instruct Vue to "hydrate" the static markup and make it interactive instead of re-rendering all the HTML that we just generated.

When createApp is change to createSSRApp and you make a fresh visit to /dashboard, it causes a hydration node mismatch causing the AppLayout.Vue to not render. Meaning there is an issue with something in AppLayout.Vue that causes client side hydration issues when using SSR.

Unless I’m missing something & am completely wrong (wouldn’t be the first time) I would think that it’s important that Jetstream is completely compatible with client side hydration that Vue/Inertia offers, given that SSR is an install option.

driesvints commented 1 year ago

Don't have time right now but will re-open this for now. Thanks

connecteev commented 1 year ago

@CamKem thank you for logging this. Having the same issue myself.

connecteev commented 1 year ago

@driesvints @CamKem one hypothesis that @RobertBoes had: this could be caused by the HTML comments in https://github.com/laravel/jetstream/blob/3.x/stubs/inertia/resources/js/Layouts/AppLayout.vue#L38 which cause the DOM mismatch between client / server.

tommy-bolger commented 1 year ago

I can confirm that this hypothesis is correct. I ran into this exact issue yesterday and removing html comments from my component templates fixed it for me.

What clued me into trying to remove html comments was this browser warning. It was the first one of many that appeared: image

The Client vnode: Symbol("v-cmt") made me realize that it was a comment tag so I took a guess and removed all html comments from my AppLayout.vue where this error was happening.

CamKem commented 1 year ago

I am not sure what others think should be or can be done about this?

Currently I'm working on a solution that uses a flag in the NPM script to use point to different defineConfig instances in config.vite.js, that sets a node ENV variable, which we then use as a conditional in app.js to switch between createApp & createSSRApp. Will also have to have the defineConfig functions stripping or leaving html comments for testing (obviously). This will allow me to have npm run dev:hydrate and npm build:hydrate so I can easily choose the build or running parameters needed to test client side hydration during different phases of client side development.

Outside of this, I think it would be beneficial if both the Jetstream & Inertia docs were updated to be a little bit more clear on SSR, specifically client side hydration. Because even if we set up run script flags to allow testing of client side hydration when also running npm run dev the instructions in the docs will need to reflect how to use the features. Alternatively if there is no testing feature added, more detailed information is still needed on the possible conflicts that the presence of the html comments that jetstream has could cause during development.

connecteev commented 1 year ago

@CamKem thanks so much for doing that. Not only do we need a solution for this, but this also needs to be clearly documented in both the Laravel / Jetstream / Breeze and Inertia docs. I believe that running SSR should also enable hydration by default.

jessarcher commented 1 year ago

I've been able to replicate the issue - thanks for the detailed steps! I don't know a whole lot about SSR and hydration, but it does strike me as problematic to build and serve the SSR bundle (npm run build + artisan inertia:start-ssr) but then also run the Vite dev server (npm run dev). Even if you could fix the mismatch on the first load, as soon as you change any code, your SSR bundle will be out of date and mismatch with the dev server, right?

If you need to modify code while running SSR and hydration, I'd imagine you'd need to use Vite in watch mode rather than HMR mode so your SSR bundle is always recompiled. Even then, you might need to stop and start the SSR server each time manually.

As for the immediate mismatch - I'm wondering whether it's just a difference between what you get from npm run build vs npm run dev. It seems like the prod build has the comments automatically stripped out. You might be able to configure this somewhere, but I don't think it prevents the core issue.

The Vue docs also mention a scenario where invalid nesting (like a <div> inside a <p>) can create a mismatch: https://vuejs.org/guide/scaling-up/ssr.html#hydration-mismatch

I'm unsure what we could document other than to discourage running a compiled SSR bundle alongside the Vite dev server. Even without hydration, I think that's going to cause unexpected results. I'm happy to reconsider if I've got it wrong, though!

CamKem commented 1 year ago

@jessarcher You are correct in your interpretation of the issue. I have done a lot of research on vite, rollupjs & terser to try and create a custom config that allows you to either allow or deny client-side hydration running & have the comments either preserved or stripped. It's possible to achieve this however I don't believe it is a good solution. For me this really comes back to needing a statement about this as a warning in both the Inertia docs & the Jetstream docs to help devs both understand and use client-side hydration & more generally SSR. Lately I have seen many people frustrated using the starter kits with Inertia due to bad SEO & they didn't feel comfortable undertaking learning about & implementing SSR in Vite/Inertia. So I feel that there should be a page in the docs that assist in this process. Possibly something like the following:

If you execute npm run build to generate your asset files, the HTML comments will be removed from the minified build assets. Subsequently, if you configure your app.js file to use createSSRApp for client-side hydration and run the Inertia SSR server using php artisan inertia:start-ssr, when you access your app in the browser, the server-served assets will be hydrated in areas with JavaScript that enables reactivity. This hydration process relies on HTML comments, which act as placeholders indicating where the reactive elements will be rendered in the DOM.

If you run npm run dev while your app is configured for client-side hydration and your Inertia SSR server is still running, Vite will rebuild the client-side assets. If there are HTML comments or other changes and potential issues in your code, you will encounter hydration node mismatch warnings in the console. This can cause certain elements, such as AppLayout.vue, to fail rendering correctly, resulting in a broken appearance for the app.

The reason for this is that the elements in your rebuilt assets do not match the server-rendered assets. When the assets are rebuilt, the presence of HTML comments in the HMR (hot module replacement) built assets can confuse the SSR server, as it relies on these comments for proper placement. As a result, the ability to hydrate and render the app can be affected.

To avoid these issues, it's important to ensure that the client-side assets and the server-rendered assets are in sync. This can be achieved by periodically stopping the Vite development server, rebuilding the assets using npm run build, and then starting the SSR server. By doing so, you allow the server to properly generate the hydration map based on the rebuilt assets, ensuring that the client-side hydration works correctly and the app renders as expected. If needed, you can then identify and resolve any issues that could cause problems for client-side hydration, such as direct usage of window references in your components. Alternatively, you can utilize composables that run after the initial rendering phase to overcome this issue.

So overall, when developing an Inertia web app with the goal of achieving server-side rendering (SSR), which is vital for SEO purposes, it is important to regularly rebuild the assets, restart the SSR server, and ensure that your code does not contain any problematic issues that would interfere with client-side hydration.

driesvints commented 1 year ago

Thank you @CamKem for that thorough investigation. Feel free to attempt a PR at the docs to see if Taylor would accept it 👍

CamKem commented 1 year ago

@driesvints I'll do a PR request when I get a minute over the weekend. Well at least if it gets rejected, I have clearly label the thread & people with similar issues can find all of the data they need to understand whats going on in the comments.

ahtinurme commented 10 months ago

@CamKem developing SSR with constant building/rebuilding both client and server is not really a good option, as it clearly takes away all the benefits HMR has to offer. So the actual solution would be to have SSR "dev mode" available, that can be used for ironing out the SSR bugs and mismatches. Since for me the SSR was the main selling point on Inertia, I have determined to get it working, but let's see how that goes. But I get it, right now the documentation is clearly half way as both Inertia nor Jetstream mention the real challenges people have to face.