Skip to content
inertia-rails-ssr logo

Inertia Rails Server-Side Rendering (SSR)

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.

SKILL.md

Full skill instructions

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
  • Vue 3.2.13+ (or install @vue/​server-renderer separately for older versions)
  • Vite Ruby for build configuration

Setup Steps

1. Create SSR Entry Point

Create app/​frontend/​ssr/​ssr.js:

Vue 3:

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:

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:

{
  "all": {
    "sourceCodeDir": "app/​frontend",
    "entrypointsDir": "entrypoints"
  },
  "development": {
    "autoBuild": true
  },
  "production": {
    "ssrBuildEnabled": true
  }
}

3. Enable SSR in Rails

Update config/​initializers/​inertia_rails.rb:

InertiaRails.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'
end

4. Update Client Entry Point

Modify app/​frontend/​entrypoints/​application.js for hydration:

Vue 3:

import { 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)
  },
})

React:

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} />)
  },
})

5. Update Layout for SSR Head

Add SSR head injection to your layout:

<!-- 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>

6. Build and Run

# Build both client and SSR bundles
bin/​vite build
bin/​vite build --ssr

# Start the SSR server
bin/​vite ssr

Production Deployment

Process Manager (systemd)

Create /​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.target
sudo systemctl enable inertia-ssr
sudo systemctl start inertia-ssr

Process Manager (PM2)

pm2 start public/​vite-ssr/​ssr.js --name inertia-ssr
pm2 save

Docker

# Run SSR server alongside Rails
CMD ["sh", "-c", "node public/​vite-ssr/​ssr.js & bundle exec puma"]

Advanced Configuration

Clustering

For better performance on multi-core systems (requires @inertiajs/​core v2.0.7+):

// ssr/​ssr.js
createServer(
  (page) => createInertiaApp({ /​* ... */ }),
  { cluster: true }  // Enable clustering
)

This runs multiple Node.js processes on a single port using round-robin request handling.

Custom Port

createServer(
  (page) => createInertiaApp({ /​* ... */ }),
  { port: 13715 }  // Custom port
)

Update Rails config to match:

config.ssr_url = 'http://localhost:13715'

Conditional SSR

Enable SSR only for specific routes:

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

Title and Meta Tags

Server-Side Meta Tags

Set meta tags in your controller:

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:

<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:

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

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

Hydration Mismatch Errors

  1. Ensure client and server render the same content
  2. Avoid browser-only APIs in initial render (use onMounted)
  3. Check for date/​time formatting differences
// 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

Missing Styles on Initial Load

Ensure CSS is included in the SSR bundle or use critical CSS extraction.

Performance Tips

  1. Keep SSR lightweight - Defer heavy computations to client
  2. Use streaming (when supported) for faster TTFB
  3. Cache SSR responses for static pages
  4. Monitor SSR latency - Add observability
# 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 DevOps & CI/CD skills

Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.

6K 472.6K
View

Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.

6K 472.6K
View

Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.

1.5K 454.3K
View
azure-kubernetes logo
DevOps & CI/CD

azure-kubernetes

Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.

1.5K 447.1K
View

Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.

92.7K 354.9K
View
just-scrape logo
DevOps & CI/CD

just-scrape

Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.

69 245K
View

Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.

462 159.7K
View

Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.

462 159.1K
View
deploy-to-vercel logo
DevOps & CI/CD

deploy-to-vercel

Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".

31.9K 146.6K
View
programmatic-seo logo
DevOps & CI/CD

programmatic-seo

Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.

53.3K 140.1K
View

Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.

63 123.5K
View

Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".

31.9K 116.1K
View

Coding & apps AI tools

Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View
Imagica logo
Coding & apps

Imagica

Build a no-code AI app in minutes.

Freemium
View
E
Coding & apps

Emergent.sh

An IDE for code migration from legacy to modern frameworks through coding agents.

Freemium
View
Wonder Dynamics logo
Coding & apps

Wonder Dynamics

Automate CGI animation in live-action scenes

Paid
View
M
Coding & apps

Mixo

Launch a website in seconds with AI.

Paid
View
AI Code Convert logo
Coding & apps

AI Code Convert

Streamline Your Coding Experience with AI Code Helper

Free
View