Loading...
Loading...
Set up Server-Side Rendering (SSR) for Inertia Rails applications. Use when you need SEO optimization, faster initial page loads, or support for users with JavaScript disabled.
npx skill4agent add cole-robertson/inertia-rails-skills inertia-rails-ssr@vue/server-rendererapp/frontend/ssr/ssr.jsimport { createInertiaApp } from '@inertiajs/vue3'
import createServer from '@inertiajs/vue3/server'
import { renderToString } from '@vue/server-renderer'
import { createSSRApp, h } from 'vue'
const pages = import.meta.glob('../pages/**/*.vue', { eager: true })
createServer((page) =>
createInertiaApp({
page,
render: renderToString,
resolve: (name) => {
const page = pages[`../pages/${name}.vue`]
if (!page) {
throw new Error(`Page not found: ${name}`)
}
return page
},
setup({ App, props, plugin }) {
return createSSRApp({
render: () => h(App, props),
}).use(plugin)
},
})
)import { createInertiaApp } from '@inertiajs/react'
import createServer from '@inertiajs/react/server'
import ReactDOMServer from 'react-dom/server'
const pages = import.meta.glob('../pages/**/*.jsx', { eager: true })
createServer((page) =>
createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: (name) => {
const page = pages[`../pages/${name}.jsx`]
if (!page) {
throw new Error(`Page not found: ${name}`)
}
return page
},
setup: ({ App, props }) => <App {...props} />,
})
)config/vite.json{
"all": {
"sourceCodeDir": "app/frontend",
"entrypointsDir": "entrypoints"
},
"development": {
"autoBuild": true
},
"production": {
"ssrBuildEnabled": true
}
}config/initializers/inertia_rails.rbInertiaRails.configure do |config|
# Enable SSR only when Vite is configured for it
config.ssr_enabled = ViteRuby.config.ssr_build_enabled
# SSR server URL (default)
config.ssr_url = 'http://localhost:13714'
endapp/frontend/entrypoints/application.jsimport { createInertiaApp } from '@inertiajs/vue3'
import { createSSRApp, h } from 'vue'
const pages = import.meta.glob('../pages/**/*.vue', { eager: true })
createInertiaApp({
resolve: (name) => pages[`../pages/${name}.vue`],
setup({ el, App, props, plugin }) {
// Use createSSRApp instead of createApp for hydration
createSSRApp({
render: () => h(App, props),
})
.use(plugin)
.mount(el)
},
})import { createInertiaApp } from '@inertiajs/react'
import { hydrateRoot } from 'react-dom/client'
const pages = import.meta.glob('../pages/**/*.jsx', { eager: true })
createInertiaApp({
resolve: (name) => pages[`../pages/${name}.jsx`],
setup({ el, App, props }) {
// Use hydrateRoot instead of createRoot for SSR
hydrateRoot(el, <App {...props} />)
},
})<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csp_meta_tag %>
<%= inertia_ssr_head %>
<%= vite_client_tag %>
<%= vite_javascript_tag 'application' %>
</head>
<body>
<%= yield %>
</body>
</html># Build both client and SSR bundles
bin/vite build
bin/vite build --ssr
# Start the SSR server
bin/vite ssr/etc/systemd/system/inertia-ssr.service[Unit]
Description=Inertia SSR Server
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/myapp/current
ExecStart=/usr/bin/node public/vite-ssr/ssr.js
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.targetsudo systemctl enable inertia-ssr
sudo systemctl start inertia-ssrpm2 start public/vite-ssr/ssr.js --name inertia-ssr
pm2 save# Run SSR server alongside Rails
CMD ["sh", "-c", "node public/vite-ssr/ssr.js & bundle exec puma"]@inertiajs/core// ssr/ssr.js
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ cluster: true } // Enable clustering
)createServer(
(page) => createInertiaApp({ /* ... */ }),
{ port: 13715 } // Custom port
)config.ssr_url = 'http://localhost:13715'class ApplicationController < ActionController::Base
# Disable SSR for admin pages
inertia_config(ssr_enabled: false)
end
class PagesController < ApplicationController
# Enable SSR for public pages
inertia_config(ssr_enabled: Rails.env.production?)
endclass PostsController < ApplicationController
def show
post = Post.find(params[:id])
render inertia: {
post: post.as_json(only: [:id, :title, :content])
}, meta: {
title: post.title,
description: post.excerpt,
og_image: post.cover_image_url
}
end
end<script setup>
import { Head } from '@inertiajs/vue3'
defineProps(['post'])
</script>
<template>
<Head>
<title>{{ post.title }}</title>
<meta name="description" :content="post.excerpt" />
<meta property="og:title" :content="post.title" />
<meta property="og:image" :content="post.coverImageUrl" />
</Head>
<article>
<h1>{{ post.title }}</h1>
<!-- ... -->
</article>
</template>import { Head } from '@inertiajs/react'
export default function Show({ post }) {
return (
<>
<Head>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
</Head>
<article>
<h1>{post.title}</h1>
{/* ... */}
</article>
</>
)
}createInertiaApp({
title: (title) => title ? `${title} - My App` : 'My App',
// ...
})curl http://localhost:13714journalctl -u inertia-ssr -fonMounted// Bad - different on server vs client
const time = new Date().toLocaleTimeString()
// Good - render after mount
const time = ref(null)
onMounted(() => {
time.value = new Date().toLocaleTimeString()
})# Log slow SSR renders
around_action :track_ssr_time, if: -> { request.inertia? }
def track_ssr_time
start = Time.current
yield
duration = Time.current - start
Rails.logger.info "SSR render: #{duration.round(3)}s" if duration > 0.1
end