rompetomp / inertia-bundle

Inertia.js server-side adapter for Symfony
MIT License
153 stars 41 forks source link

Throws exception when navigating to a page via browser address bar #51

Open secondmanveran opened 1 year ago

secondmanveran commented 1 year ago

When using the Link component pages navigate correctly but when you input a page via the address bar I get the following exception:

12:36:38.709 critical | Uncaught PHP Exception Twig\Error\RuntimeError: "An exception has been thrown during the rendering of a template ("Response body is empty.")." at /Users/xxx/Sites/symfony/app/templates/app.html.twig line 5

It looks like it's dying at:

{{ inertiaHead(page) }}

Any ideas what's up, or any additional info you need?

neluba commented 1 year ago

Are you using SSR? Errors on the server side are often harder to track down, because the console output is worse than in the browser. You could try to turn it off and check if you get an error in the browser console on page load.

secondmanveran commented 1 year ago

I am. I'll try disabling it and see if that resolves it.

secondmanveran commented 1 year ago

@neluba

Yep that seems to resolve the issue. No errors at all in the console when running without SSR.

What would be my next step in debugging why I'm not getting a response body with SSR turned on?

secondmanveran commented 1 year ago

Wow, so check this out.

I opened Rompetomp\InertiaBundle\Ssr\HttpGateway::class

And under the dispatch method I added a dump to see what's going on:

try {
  $response = $this->httpClient->request(
    'POST',
    $this->interia->getSsrUrl(),
    [
      'headers' => [
        'Content-Type: application/json',
        'Accept: application/json',
      ],
      'body' => json_encode($page),
    ]
  );
  // to see the array of the response
  dd($response->toArray()); 
} catch (Exception $e) {
  return null;
}

And with this dump in place... all the pages load and render correctly.

What the heck???

Could this be something to do with using the Inertia > 1.0 version? No need to actually return a Response object?

neluba commented 1 year ago

@secondmanveran Does your node server output any errors? Inertia v1 shouldn't be the problem, because I'm using it as well.

You should also see the dump output if you put the dd() at this location, except if you turned off SSR.

You could also try to use ts-node-dev in development, if you don't already use it, and see if it gives you more output. The output is often not very helpful, but better than nothing. It looks like this

Inertia SSR server started.
[Vue warn]: Property "foo" was accessed during render but is not defined on instance.
TypeError: Cannot read properties of undefined (reading 'length')
    at ssrRender (/.../public/build-ssr/ssr.js:151323:16)

The problem could also be in your ssr entrypoint, if you can't reproduce the problem without SSR.

Here is my version without all the extra vue plugins

createServer((page) =>
  createInertiaApp({
    page,
    render: renderToString,
    resolve: (name) => {
      const pageInternal = require(`@/pages/${name}`).default;
      pageInternal.layout = pageInternal.layout || DefaultLayout;
      return pageInternal;
    },
    setup({ el, app, props, plugin }) {
      const vueApp = createSSRApp({ render: () => h(app, props) })
        .use(plugin);

      return vueApp;
    },
  })
);
secondmanveran commented 1 year ago

@neluba

I've had a bit of progress but still getting weird results all around.

Now I'm getting weird rendering with no css on any initial page load.

Then when I click an Inertia link, it will load up the requested component and render everything perfectly.

Any initial page:

Screenshot 2023-05-24 at 5 38 02 PM

Then when I navigate via links:

Screenshot 2023-05-24 at 5 38 51 PM

I put all my dependencies in a plugin file that looks like so:

import { Head, Link } from '@inertiajs/vue3'
import { Routing } from 'fos'
import mitt from 'mitt'
import axios from 'axios'
import dayjs from 'dayjs'
import routes from './routes.json'

Routing.setRoutingData(routes)

export const zuma = {
  install(app) {
    axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
    axios.defaults.withCredentials = true

    const router = (name, params, absolute = true) =>
      Routing.generate(name, params, absolute)

    app.mixin({
      methods: {
        route: router
      }
    })

    const is_null = obj => {
      return !obj && typeof obj === 'object'
    }

    const isObject = obj => {
      return obj === Object(obj)
    }

    app.provide('route', router)
    app.provide('emitter', mitt())
    app.provide('dayjs', dayjs)
    app.provide('axios', axios)
    app.provide('isObject', isObject)
    app.provide('is_null', is_null)

    app.component('Head', Head)
    app.component('Link', Link)
  }
}

That way I can ensure both app and ssr contain the same dependencies.

My ssr server looks like so:

import { createInertiaApp } from '@inertiajs/vue3'
import createServer from '@inertiajs/vue3/server'
import { renderToString } from '@vue/server-renderer'
import { createSSRApp, h } from 'vue'
import { resolvePageComponent } from './resolvePageComponent.js'
import { zuma } from './plugin.js'

import '../css/app.css'

const pages = import.meta.glob('../pages/**/*.{vue,md}', { eager: true })

createServer(
  page =>
    createInertiaApp({
      page,
      render: renderToString,
      resolve: async name => {
        return await resolvePageComponent(`../pages/${name}.vue`, pages)
      },
      setup({ App, props, plugin }) {
        return createSSRApp({
          render: () => h(App, props)
        })
          .use(plugin)
          .use(zuma)
      }
    }),
  import.meta.env.VITE_SSR_PORT
)

The resolvePageComponent is the same as what comes from the Laravel Inertia helper.

I am using client side hydration in my app.

All this works fine in my Laravel apps, I would have thought Symfony would be farther ahead of this, but I think the fact that Encore is so widely promoted is stopping the team from moving forward on the Vite trend.

neluba commented 1 year ago

@secondmanveran Are you using Vite? I never used it and symfony and would wish that the symfony team would officially support it. Styles are also my biggest pain point with SSR, because I can't get the SSR server to output the css chunks from single file components. And it also tries to load it's own generated main.css file, instead of of a shared one with the client code.

A workaround to at least load the css entrypoint in SSR are these few lines in the SSR webpack encore config.

Encore.setOutputPath("public/build-ssr/")
  .setPublicPath(process.env.DEV_ENV === "DEV_SERVER" ? "https://localhost:9038/build" : "/build")
  .setManifestKeyPrefix("build/")

This will generate asset links with /build instead of /build-ssr.

The environment variable check helps with the encore dev server. Port 9038 is the port in my project, encore usually uses 9000. I start my ssr watcher with this script in the package json, to prepend the env var:

{
    "scripts": {
        "watch:ssr": "encore dev --watch -c ./webpack.ssr.config.js",
        "dev-server:ssr": "DEV_ENV=DEV_SERVER encore dev --watch -c ./webpack.ssr.config.js",
        "build:ssr": "NODE_ENV=production encore production --progress -c ./webpack.ssr.config.js"
    }
}

The result is ok, but not great, because it only helps a little. I need the ssr part for SEO reasons in production for my main project and hide most of the ui, until the client rendered the ui with additional css chunks loaded through the webpack runtime.

It does work well with tailwind though in another project of mine, because I only have a single css file anyway.

secondmanveran commented 1 year ago

@neluba

Yes I tried to use Encore just so I could keep in line with the current "Symfony way" and it wouldn't compile Inertia > 1, so I went back to Vite, which is what I normally use. I can't stand webpack, it's just so bloated and time consuming. ES modules are the way.

I don't ever use SFC css normally so let me try getting rid of this default Symfony start page and we'll see where we are then.

secondmanveran commented 1 year ago

Yeah I give up. I'll just stick with Laravel.