Skip to content

Inertia SSR #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 14, 2022
Merged

Inertia SSR #74

merged 4 commits into from
Feb 14, 2022

Conversation

BrandonShar
Copy link
Collaborator

@BrandonShar BrandonShar commented Feb 12, 2022

Long overdue, we're finally readying for Inertia Rails SSR!

First off, a huge thanks to @zealot128 for their work on #73 . I had already started this version so I wanted to push forward with it, but their code was a great help in ensuring we were all thinking about things in a similar way.

Part of the hold up here ha been that Rail's Webpacker is not quite as fluid as Laravel Mix so I wanted to wait until I felt like we had a solid way of recommending the JS setup. After a lot of tinkering, I'm happy to say that I think we do!

First off, the InertiaRails portion of this is very simple, InertiaRails gets 2 new config values that can be set (the values shown are the defaults):

Backend

InertiaRails.configure do |config|
   config.ssr_enabled = false
   config.ssr_url = 'http://localhost:13714'
end

Frontend

Now comes the harder part, the javascript:

First things first, let's add a new dependency.

yarn add webpack-node-externals

More information can be found in the Inertia SSR docs: https://inertiajs.com/server-side-rendering

Unfortunately, Laravel Mix doesn't exist for rails, so the docs can't help us much further (though they do give us the contents of one more file).

I created/modified the following files:

Create app/javascript/ssr/ssr.jsx

import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { createInertiaApp } from '@inertiajs/inertia-react'
import createServer from '@inertiajs/server'

createServer((page) => createInertiaApp({
  page,
  render: ReactDOMServer.renderToString,
  resolve: name => require(`../Pages/${name}`),
  setup: ({ App, props }) => <App {...props} />,
}))

This comes directly from the Inertia docs and serves as the entry point for Inertia SSR.

Create config/webpack/ssr.js

process.env.NODE_ENV = 'ssr'

const { environment } = require('@rails/webpacker')
const nodeExternals = require('webpack-node-externals')

const config = environment.toWebpackConfig();

module.exports = {
  ...config,
  output: {
    ...config['output'],
    filename: 'ssr.js',
  },
  target: 'node',
  externals: [nodeExternals()],
}

This file is unique to rails but is similar in spirit to Inertia's ssr.mix.js file. The basic idea is that the SSR node process is distinct from our usual JS, so we want to configure it a bit differently.

Edit config/webpacker.yml and add the following entry:

ssr:
  <<: *default
  compile: false
  extract_css: true

  source_path: app/javascript
  source_entry_path: ssr
  public_root_path: public
  public_output_path: ssr

This entry will allow us to keep our ssr js separate since it's not really a pack in the rails sense.

Edit babel.config.js
Change the individual lines that look like

var validEnv = ['development', 'test', 'production']
var isProductionEnv = api.env('production')

to

var validEnv = ['development', 'test', 'production', 'ssr']
var isProductionEnv = api.env('production') || api.env('ssr')

This let's us specify our new ssr node env, but otherwise treat it like production.

Edit app/views/layouts/application.html.erb and add the following line inside of the head tags

<%= inertia_headers %>

This allows Inertia SSR to add to the document head

And finally

Edit package.json and add (or modify if you already have one) to add a scripts section

"scripts": {
    "ssr": "NODE_ENV=ssr RAILS_ENV=ssr ./bin/webpack && node ./public/ssr/ssr.js"
  }

This will allow you to run the SSR server locally with the command yarn run ssr (but keep in mind that it does NOT hot reload, you will need to kill and re-run this command when you make JS changes!)

And that should be that! Now kickback and disable your javascript!

If these changes work well for most users, we'll get them moved into the templates.

Outstanding questions

For those of you who need SSR, do you prefer to use SSR in development? As of right now, the best workflow from an ease of use standpoint would be to use traditional Inertia in development and SSR only in production. Obviously, this runs of the risk of accidentally using client only javascript or otherwise breaking the production build despite working fine in development. For those of you using this, is that an acceptable tradeoff or should we invest some time in making SSR in development a better experience?

@BrandonShar BrandonShar requested a review from bknoles February 12, 2022 04:43
@BrandonShar BrandonShar merged commit 14ecdfb into inertiajs:master Feb 14, 2022
@BrandonShar BrandonShar deleted the ssr branch February 14, 2022 22:15
@bknoles
Copy link
Collaborator

bknoles commented Feb 16, 2022

I'm curious to know how Inertia based SSR does with React hydration issues. We've seen this in a couple places:

  • If the server rendered HTML doesn't match the client side "hydrated" HTML. I've seen this when pre-rendering pages at "dynamic" URLS. An example would be using react-snap with a React router based app that fetches a list of "posts" from an API and has a dynamically created route for each post. Since Rails actually handles all the routing, I would not expect this to be an issue for Inertia SSR.
  • If the React app creates HTML outside of the React app root, then we get "orphaned" DOM nodes. We've seen this plague apps with portal based modals. I would assume an Inertia app with SSR _would) be vulnerable to this type of hydration issue.

If I can carve out some time, I'd like to test those on a dummy app. In the meantime, any theory based thoughts on how Inertia will fare here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants