inertia-rails-ssr
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInertia Rails Server-Side Rendering (SSR)
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.
服务端渲染(SSR)会在服务器上预渲染JavaScript页面,向访问者交付完全渲染好的HTML。这能提升SEO效果,加快初始页面加载速度,并且在禁用JavaScript的情况下仍能支持基础导航。
When to Use SSR
何时使用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
- SEO关键页面 - 落地页、博客文章、产品页
- 社交分享 - 需要正确Open Graph元标签的页面
- 网络连接缓慢 - 用户在JavaScript加载完成前就能看到内容
- 可访问性 - 无需JavaScript即可使用基础功能
Requirements
前提条件
- Node.js must be available on your server
- Vue 3.2.13+ (or install separately for older versions)
@vue/server-renderer - Vite Ruby for build configuration
- 服务器上必须安装Node.js
- Vue 3.2.13+(旧版本需单独安装)
@vue/server-renderer - 使用Vite Ruby进行构建配置
Setup Steps
配置步骤
1. Create SSR Entry Point
1. 创建SSR入口文件
Create :
app/frontend/ssr/ssr.jsVue 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} />,
})
)创建文件:
app/frontend/ssr/ssr.jsVue 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
2. 配置Vite Ruby
Update :
config/vite.jsonjson
{
"all": {
"sourceCodeDir": "app/frontend",
"entrypointsDir": "entrypoints"
},
"development": {
"autoBuild": true
},
"production": {
"ssrBuildEnabled": true
}
}更新文件:
config/vite.jsonjson
{
"all": {
"sourceCodeDir": "app/frontend",
"entrypointsDir": "entrypoints"
},
"development": {
"autoBuild": true
},
"production": {
"ssrBuildEnabled": true
}
}3. Enable SSR in Rails
3. 在Rails中启用SSR
Update :
config/initializers/inertia_rails.rbruby
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更新文件:
config/initializers/inertia_rails.rbruby
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'
end4. Update Client Entry Point
4. 更新客户端入口文件
Modify for hydration:
app/frontend/entrypoints/application.jsVue 3:
javascript
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:
javascript
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/frontend/entrypoints/application.jsVue 3:
javascript
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:
javascript
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
5. 在布局中添加SSR头部注入
Add SSR head injection to your layout:
erb
<!-- 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>erb
<!-- 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
6. 构建并运行
bash
undefinedbash
undefinedBuild both client and SSR bundles
Build both client and SSR bundles
bin/vite build
bin/vite build --ssr
bin/vite build
bin/vite build --ssr
Start the SSR server
Start the SSR server
bin/vite ssr
undefinedbin/vite ssr
undefinedProduction Deployment
生产环境部署
Process Manager (systemd)
进程管理器(systemd)
Create :
/etc/systemd/system/inertia-ssr.serviceini
[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.targetbash
sudo systemctl enable inertia-ssr
sudo systemctl start inertia-ssr创建文件:
/etc/systemd/system/inertia-ssr.serviceini
[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.targetbash
sudo systemctl enable inertia-ssr
sudo systemctl start inertia-ssrProcess Manager (PM2)
进程管理器(PM2)
bash
pm2 start public/vite-ssr/ssr.js --name inertia-ssr
pm2 savebash
pm2 start public/vite-ssr/ssr.js --name inertia-ssr
pm2 saveDocker
Docker
dockerfile
undefineddockerfile
undefinedRun SSR server alongside Rails
Run SSR server alongside Rails
CMD ["sh", "-c", "node public/vite-ssr/ssr.js & bundle exec puma"]
undefinedCMD ["sh", "-c", "node public/vite-ssr/ssr.js & bundle exec puma"]
undefinedAdvanced Configuration
高级配置
Clustering
集群模式
For better performance on multi-core systems (requires v2.0.7+):
@inertiajs/corejavascript
// 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.
在多核系统中提升性能(需 v2.0.7+):
@inertiajs/corejavascript
// ssr/ssr.js
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ cluster: true } // Enable clustering
)此配置会使用轮询请求处理方式,在单个端口上运行多个Node.js进程。
Custom Port
自定义端口
javascript
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ port: 13715 } // Custom port
)Update Rails config to match:
ruby
config.ssr_url = 'http://localhost:13715'javascript
createServer(
(page) => createInertiaApp({ /* ... */ }),
{ port: 13715 } // Custom port
)同步更新Rails配置:
ruby
config.ssr_url = 'http://localhost:13715'Conditional SSR
条件式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仅为特定路由启用SSR:
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?)
endTitle and Meta Tags
标题与元标签
Server-Side Meta Tags
服务端元标签
Set meta tags in your controller:
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在控制器中设置元标签:
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
endUsing Head Component
使用Head组件
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>
</>
)
}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',
// ...
})javascript
createInertiaApp({
title: (title) => title ? `${title} - My App` : 'My App',
// ...
})Troubleshooting
故障排除
SSR Server Not Responding
SSR服务器无响应
- Check if the SSR server is running:
curl http://localhost:13714 - Check logs:
journalctl -u inertia-ssr -f - Verify the port matches your Rails config
- 检查SSR服务器是否运行:
curl http://localhost:13714 - 查看日志:
journalctl -u inertia-ssr -f - 验证端口是否与Rails配置一致
Hydration Mismatch Errors
水合渲染不匹配错误
- Ensure client and server render the same content
- Avoid browser-only APIs in initial render (use )
onMounted - 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()
})- 确保客户端与服务器渲染的内容一致
- 避免在初始渲染中使用仅浏览器可用的API(使用)
onMounted - 检查日期/时间格式差异
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
内存泄漏
- Avoid global state mutations in SSR
- Use request-scoped state only
- Monitor Node.js memory usage
- 避免在SSR中修改全局状态
- 仅使用请求作用域的状态
- 监控Node.js内存使用情况
Missing Styles on Initial Load
初始加载时样式缺失
Ensure CSS is included in the SSR bundle or use critical CSS extraction.
确保CSS已包含在SSR包中,或使用关键CSS提取。
Performance Tips
性能优化建议
- Keep SSR lightweight - Defer heavy computations to client
- Use streaming (when supported) for faster TTFB
- Cache SSR responses for static pages
- Monitor SSR latency - Add observability
ruby
undefined- 保持SSR轻量化 - 将繁重计算延迟到客户端执行
- 使用流式渲染(支持时)以加快TTFB
- 缓存SSR响应(针对静态页面)
- 监控SSR延迟 - 添加可观测性
ruby
undefinedLog slow SSR renders
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
undefinedaround_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
undefined