inertia-rails-ssr
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.
Works with
---
name: inertia-rails-ssr
description: 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.
license: MIT
---
# Inertia Rails Server-Side Rendering (SSR)
Server-Side Rendering pre-renders JavaScript pages on the server, delivering fully rendered HTML to visitors. This improves SEO, enables faster initial page loads, and allows basic navigation even with JavaScript disabled.
## When to Use SSR
- **SEO-critical pages** - Landing pages, blog posts, product pages
- **Social sharing** - Pages that need proper Open Graph meta tags
- **Slow connections** - Users see content before JavaScript loads
- **Accessibility** - Basic functionality without JavaScript
## Requirements
- **Node.js** must be available on your server
- **Vite Ruby** for build configuration
- **`@inertiajs/vite`** plugin (recommended for v3)
## Recommended: Vite Plugin Setup (v3)
The `@inertiajs/vite` plugin simplifies SSR dramatically. In development, SSR runs through the Vite dev server automatically — no separate Node.js process needed.
### 1. Install the Vite Plugin
```bash
npm install @inertiajs/vite
```
### 2. Configure Vite
Update your `vite.config.js`:
**React:**
```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import inertia from '@inertiajs/vite'
export default defineConfig({
plugins: [
inertia(),
react(),
],
})
```
**Vue 3:**
```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import inertia from '@inertiajs/vite'
export default defineConfig({
plugins: [
inertia(),
vue(),
],
})
```
**Svelte:**
```javascript
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'
export default defineConfig({
plugins: [
inertia(),
svelte(),
],
})
```
### 3. Update Entry Point for Hydration
The client entry point should use hydration instead of full rendering when SSR is enabled.
**React:**
```javascript
import { createInertiaApp } from '@inertiajs/react'
import { hydrateRoot } from 'react-dom/client'
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('../pages/**/*.tsx', { eager: true })
return pages[`../pages/${name}.tsx`]
},
setup({ el, App, props }) {
hydrateRoot(el, <App {...props} />)
},
})
```
**Vue 3:**
```javascript
import { createInertiaApp } from '@inertiajs/vue3'
import { createSSRApp, h } from 'vue'
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('../pages/**/*.vue', { eager: true })
return pages[`../pages/${name}.vue`]
},
setup({ el, App, props, plugin }) {
createSSRApp({
render: () => h(App, props),
})
.use(plugin)
.mount(el)
},
})
```
### 4. Enable SSR in Rails
```ruby
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
config.ssr_enabled = true
# In development, SSR is handled by the Vite dev server automatically.
# Set ssr_url to nil to auto-detect from Vite.
config.ssr_url = nil
# Optional: only enable SSR when the bundle exists (production)
# config.ssr_bundle = Rails.root.join('public/vite-ssr/ssr.js')
# Optional: cache SSR responses
# config.ssr_cache = true
# config.ssr_cache = { expires_in: 1.hour }
# Optional: handle SSR errors gracefully
config.ssr_raise_on_error = !Rails.env.production?
config.on_ssr_error = ->(error, page) {
Rails.logger.error "SSR error for #{page[:component]}: #{error.message}"
}
end
```
### 5. Start Development
```bash
# Terminal 1: Rails server
bin/rails server
# Terminal 2: Vite dev server (handles SSR automatically)
bin/vite dev
```
That's it! No separate SSR server process needed in development.
## Manual SSR Setup (Without Vite Plugin)
If you can't use the Vite plugin, you can set up SSR manually with a separate entry point.
### 1. Create SSR Entry Point
Create `app/frontend/ssr/ssr.js`:
**Vue 3:**
```javascript
import { 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)
},
})
)
```
**React:**
```javascript
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} />,
})
)
```
### 2. Configure Vite Ruby
Update `config/vite.json`:
```json
{
"all": {
"sourceCodeDir": "app/frontend",
"entrypointsDir": "entrypoints"
},
"development": {
"autoBuild": true
},
"production": {
"ssrBuildEnabled": true
}
}
```
### 3. Enable SSR in Rails
```ruby
InertiaRails.configure do |config|
config.ssr_enabled = ViteRuby.config.ssr_build_enabled
config.ssr_url = 'http://localhost:13714'
end
```
### 4. Build and Run
```bash
bin/vite build
bin/vite build --ssr
bin/vite ssr
```
## Production Deployment
In production, you still need to build the SSR bundle and run a Node.js server (even with the Vite plugin).
### Build for Production
```bash
bin/vite build
bin/vite build --ssr
```
### Process Manager (systemd)
Create `/etc/systemd/system/inertia-ssr.service`:
```ini
[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.target
```
```bash
sudo systemctl enable inertia-ssr
sudo systemctl start inertia-ssr
```
### Process Manager (PM2)
```bash
pm2 start public/vite-ssr/ssr.js --name inertia-ssr
pm2 save
```
### Docker
```dockerfile
CMD ["sh", "-c", "node public/vite-ssr/ssr.js & bundle exec puma"]
```
## Advanced Configuration
### SSR Response Caching
Cache SSR responses to reduce Node.js load:
```ruby
InertiaRails.configure do |config|
# Simple boolean — uses default Rails cache store
config.ssr_cache = true
# Or with options
config.ssr_cache = { expires_in: 1.hour }
end
```
### SSR Bundle Detection
Only attempt SSR when the bundle exists:
```ruby
InertiaRails.configure do |config|
config.ssr_bundle = Rails.root.join('public/vite-ssr/ssr.js')
# Can also be an array of paths to check
# config.ssr_bundle = [
# Rails.root.join('public/vite-ssr/ssr.js'),
# Rails.root.join('public/vite-ssr/ssr.mjs'),
# ]
end
```
### Clustering
For better performance on multi-core systems:
```javascript
// ssr/ssr.js
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ cluster: true }
)
```
### Custom Port
```javascript
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ port: 13715 }
)
```
### Conditional SSR
Enable SSR only for specific routes:
```ruby
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?)
end
# Per-action with a lambda
class PostsController < ApplicationController
inertia_config(ssr_enabled: ->(request) {
request.path.start_with?('/blog')
})
end
```
## Title and Meta Tags
### Server-Managed Meta Tags (v3)
Set meta tags from your controller using the `inertia_meta` helper or the `meta:` render option:
```ruby
class 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
```
### Using Head Component
**Vue 3:**
```vue
<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>
```
**React:**
```jsx
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>
</>
)
}
```
### Default Title Template
```javascript
createInertiaApp({
title: (title) => title ? `${title} - My App` : 'My App',
// ...
})
```
## Troubleshooting
### SSR Server Not Responding
1. Check if the SSR server is running: `curl http://localhost:13714`
2. Check logs: `journalctl -u inertia-ssr -f`
3. Verify the port matches your Rails config
4. If using the Vite plugin in dev, ensure the Vite dev server is running
### Hydration Mismatch Errors
1. Ensure client and server render the same content
2. Avoid browser-only APIs in initial render (use `onMounted`/`useEffect`)
3. Check for date/time formatting differences
```javascript
// 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()
})
```
### Memory Leaks
1. Avoid global state mutations in SSR
2. Use request-scoped state only
3. Monitor Node.js memory usage
### SSR Fails Silently
Configure error handling to catch and log SSR failures:
```ruby
InertiaRails.configure do |config|
config.ssr_raise_on_error = !Rails.env.production?
config.on_ssr_error = ->(error, page) {
Rails.logger.error "SSR error: #{error.message}"
Sentry.capture_exception(error) if defined?(Sentry)
}
end
```
## Performance Tips
1. **Keep SSR lightweight** - Defer heavy computations to client
2. **Use caching** - Enable `ssr_cache` for static/semi-static pages
3. **Use clustering** - Enable multi-worker mode for production
4. **Monitor latency** - Track SSR render times
5. **Bundle detection** - Use `ssr_bundle` to gracefully fall back to CSR
```ruby
# 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
```More Backend Frameworks skills
git-guardrails-claude-code
mattpocock/skills
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
azure-compute
microsoft/azure-skills
Azure VM/VMSS router. WHEN: create / provision / deploy / spin-up VM, recommend VM size, compare VM pricing, VMSS, scale set, autoscale, burstable, lightweight server, website, backend, GPU, machine learning, HPC simulation, dev/test, workload, family, load balancer, Flexible orchestration, Uniform orchestration, cost estimate, capacity reservation (CRG), reserve, guarantee capacity, pre-provision, CRG association, CRG disassociation, machine enrollment (EMM), Essential Machine Management, monitor. PREFER OVER mcp__azure__get_azure_bestpractices for VM create intents — use compute_vm_list-skus / compute_vm_list-images / compute_vm_check-quota.
azure-cloud-migrate
microsoft/azure-skills
Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration.

