mcpField
I
inertia-rails-ssr

inertia-rails-ssr

github.com/cole-robertson开发3878 次安装30 周前更新
代码仓库

关于 inertia-rails-ssr

这是一份使用 Vite Ruby 和 Node.js 为 Inertia Rails 页面配置服务端渲染的指南,涵盖 Vite 插件与手动配置、生产部署及故障排查。

主要功能

  1. 01通过 Vite 插件或手动配置 SSR
  2. 02设置 React、Vue 3 和 Svelte 入口
  3. 03在开发与生产环境构建和运行 SSR
  4. 04配置缓存、集群和条件渲染
  5. 05管理标题与元标签
  6. 06排查水合与服务端错误

适用场景

  1. 01在服务器端渲染 Rails 公共页面
  2. 02准备 SEO 和社交分享元数据
  3. 03部署和管理 SSR 进程
  4. 04诊断水合不匹配或 SSR 故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cole-robertson/inertia-rails-skills --skill inertia-rails-ssr

安装后在 Claude Code 中输入/inertia-rails-ssr确认已加载。

详细介绍

Inertia Rails 服务端渲染(SSR)

服务端渲染会在服务器上预渲染 JavaScript 页面,并向访客提供完整渲染后的 HTML。这有助于提升 SEO、加快页面初次加载,并让用户即使在禁用 JavaScript 时也能进行基本导航。

何时使用 SSR

  • SEO 至关重要的页面 - 着陆页、博客文章、产品页面
  • 社交分享 - 需要正确 Open Graph 元标签的页面
  • 网络连接缓慢 - 用户可在 JavaScript 加载前看到内容
  • 无障碍访问 - 无需 JavaScript 即可使用基本功能

要求

  • 服务器上必须能够使用 Node.js
  • 用于构建配置的 Vite Ruby
  • @inertiajs/vite 插件(v3 推荐)

推荐:Vite 插件配置(v3)

@inertiajs/vite 插件可大幅简化 SSR。在开发环境中,SSR 会自动通过 Vite 开发服务器运行,无需单独的 Node.js 进程。

1. 安装 Vite 插件
npm install @inertiajs/vite
2. 配置 Vite

更新 vite.config.js:

React:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import inertia from '@inertiajs/vite'

export default defineConfig({
  plugins: [
    inertia(),
    react(),
  ],
})

Vue 3:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import inertia from '@inertiajs/vite'

export default defineConfig({
  plugins: [
    inertia(),
    vue(),
  ],
})

Svelte:

import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import inertia from '@inertiajs/vite'

export default defineConfig({
  plugins: [
    inertia(),
    svelte(),
  ],
})
3. 更新入口点以支持激活

启用 SSR 后,客户端入口点应使用激活,而不是完整渲染。

React:

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:

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. 在 Rails 中启用 SSR
# 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. 启动开发环境
# Terminal 1: Rails server
bin/rails server

# Terminal 2: Vite dev server (handles SSR automatically)
bin/vite dev

就这么简单!开发环境中无需单独的 SSR 服务进程。

手动配置 SSR(不使用 Vite 插件)

如果无法使用 Vite 插件,可以通过单独的入口点手动配置 SSR。

1. 创建 SSR 入口点

创建 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. 配置 Vite Ruby

更新 config/vite.json:

{
  "all": {
    "sourceCodeDir": "app/frontend",
    "entrypointsDir": "entrypoints"
  },
  "development": {
    "autoBuild": true
  },
  "production": {
    "ssrBuildEnabled": true
  }
}
3. 在 Rails 中启用 SSR
InertiaRails.configure do |config|
  config.ssr_enabled = ViteRuby.config.ssr_build_enabled
  config.ssr_url = 'http://localhost:13714'
end
4. 构建并运行
bin/vite build
bin/vite build --ssr
bin/vite ssr

生产环境部署

在生产环境中,仍需构建 SSR 包并运行 Node.js 服务(即使使用了 Vite 插件)。

为生产环境构建
bin/vite build
bin/vite build --ssr
进程管理器(systemd)

创建 /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
进程管理器(PM2)
pm2 start public/vite-ssr/ssr.js --name inertia-ssr
pm2 save
Docker
CMD ["sh", "-c", "node public/vite-ssr/ssr.js & bundle exec puma"]

高级配置

SSR 响应缓存

缓存 SSR 响应以降低 Node.js 负载:

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 包检测

仅当包存在时才尝试 SSR:

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
集群

在多核系统上提升性能:

// ssr/ssr.js
createServer(
  (page) => createInertiaApp({ /* ... */ }),
  { cluster: true }
)
自定义端口
createServer(
  (page) => createInertiaApp({ /* ... */ }),
  { port: 13715 }
)
条件式 SSR

仅对特定路由启用 SSR:

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

标题和元标签

服务端管理的元标签(v3)

使用 inertia_meta 辅助方法或 meta: 渲染选项,从控制器设置元标签:

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
使用 Head 组件

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>
    </>
  )
}
默认标题模板
createInertiaApp({
  title: (title) => title ? `${title} - My App` : 'My App',
  // ...
})

故障排除

SSR 服务无响应
  1. 检查 SSR 服务是否正在运行:curl http://localhost:13714
  2. 查看日志:journalctl -u inertia-ssr -f
  3. 确认端口与 Rails 配置一致
  4. 如果在开发环境中使用 Vite 插件,请确保 Vite 开发服务器正在运行
激活不匹配错误
  1. 确保客户端和服务端渲染的内容相同
  2. 避免在初次渲染时使用仅限浏览器的 API(使用 onMounted/useEffect)
  3. 检查日期/时间格式是否存在差异
// 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()
})
内存泄漏
  1. 避免在 SSR 中修改全局状态
  2. 仅使用请求作用域的状态
  3. 监控 Node.js 内存使用情况
SSR 静默失败

配置错误处理,以捕获并记录 SSR 故障:

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

性能提示

  1. 保持 SSR 轻量 - 将繁重的计算延后到客户端
  2. 使用缓存 - 为静态/半静态页面启用 ssr_cache
  3. 使用集群 - 在生产环境中启用多工作进程模式
  4. 监控延迟 - 跟踪 SSR 渲染耗时
  5. 检测包 - 使用 ssr_bundle,以便在必要时平稳回退到 CSR
# 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

评论

支持 Markdown