Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Vue SSR教程,详解Nuxt.js、服务端渲染、同构应用。包含完整SSR方案,适合Vue.js开发者快速掌握服务端渲染技术。
核心关键词:Vue SSR 2024、Nuxt.js、服务端渲染、同构应用、Vue SSR优化
长尾关键词:Vue服务端渲染怎么实现、Nuxt.js最佳实践、SSR性能优化策略、同构应用开发指南、Vue SSR SEO优化
通过本节Vue SSR深度教程,你将系统性掌握:
为什么选择服务端渲染?这是现代Web开发的重要决策点。SSR能够显著提升首屏加载速度、改善SEO效果、提供更好的用户体验,特别是对于内容驱动的应用。合理的SSR架构是企业级Vue应用的重要技术选择。
💡 架构建议:根据应用特性选择SSR策略,考虑静态生成、增量静态再生成等混合方案
Nuxt.js是Vue.js的SSR框架,提供开箱即用的SSR解决方案:
<!-- pages/index.vue - Nuxt.js首页组件 -->
<template>
<div class="ssr-demo">
<div class="demo-section">
<h1>Vue SSR演示应用</h1>
<!-- 服务端渲染内容 -->
<div class="ssr-content">
<h2>服务端渲染内容</h2>
<div class="content-grid">
<div
v-for="post in posts"
:key="post.id"
class="post-card"
>
<div class="post-header">
<h3>{{ post.title }}</h3>
<span class="post-date">{{ formatDate(post.createdAt) }}</span>
</div>
<div class="post-excerpt">{{ post.excerpt }}</div>
<div class="post-meta">
<span class="post-author">作者: {{ post.author }}</span>
<span class="post-views">阅读: {{ post.views }}</span>
</div>
</div>
</div>
</div>
<!-- 客户端交互内容 -->
<div class="client-content">
<h2>客户端交互内容</h2>
<div class="interaction-demo">
<div class="counter-demo">
<h4>计数器演示</h4>
<div class="counter-display">
<span class="counter-value">{{ counter }}</span>
<div class="counter-controls">
<button @click="decrement" class="counter-btn">-</button>
<button @click="increment" class="counter-btn">+</button>
</div>
</div>
</div>
<div class="form-demo">
<h4>表单交互</h4>
<form @submit.prevent="submitForm" class="demo-form">
<div class="form-group">
<label for="name">姓名:</label>
<input
id="name"
v-model="form.name"
type="text"
required
class="form-input"
>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input
id="email"
v-model="form.email"
type="email"
required
class="form-input"
>
</div>
<button type="submit" class="submit-btn">提交</button>
</form>
</div>
</div>
</div>
<!-- 性能监控 -->
<div class="performance-monitor">
<h2>SSR性能监控</h2>
<div class="performance-metrics">
<div class="metric-card">
<div class="metric-title">服务端渲染时间</div>
<div class="metric-value">{{ ssrRenderTime }}ms</div>
<div class="metric-description">服务器生成HTML的时间</div>
</div>
<div class="metric-card">
<div class="metric-title">首次内容绘制</div>
<div class="metric-value">{{ firstContentfulPaint }}ms</div>
<div class="metric-description">用户看到内容的时间</div>
</div>
<div class="metric-card">
<div class="metric-title">可交互时间</div>
<div class="metric-value">{{ timeToInteractive }}ms</div>
<div class="metric-description">页面可以响应用户交互</div>
</div>
<div class="metric-card">
<div class="metric-title">水合时间</div>
<div class="metric-value">{{ hydrationTime }}ms</div>
<div class="metric-description">客户端接管页面的时间</div>
</div>
</div>
<div class="performance-timeline">
<h4>加载时间线</h4>
<div class="timeline-container">
<div class="timeline-bar">
<div
class="timeline-segment ssr"
:style="{ width: getTimelineWidth('ssr') + '%' }"
>
SSR
</div>
<div
class="timeline-segment download"
:style="{ width: getTimelineWidth('download') + '%' }"
>
下载
</div>
<div
class="timeline-segment hydration"
:style="{ width: getTimelineWidth('hydration') + '%' }"
>
水合
</div>
<div
class="timeline-segment interactive"
:style="{ width: getTimelineWidth('interactive') + '%' }"
>
交互
</div>
</div>
<div class="timeline-labels">
<span>0ms</span>
<span>{{ ssrRenderTime }}ms</span>
<span>{{ firstContentfulPaint }}ms</span>
<span>{{ timeToInteractive }}ms</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SSRDemo',
// Nuxt.js异步数据获取
async asyncData({ $axios, params, error }) {
try {
// 服务端获取数据
const { data: posts } = await $axios.get('/api/posts')
return {
posts: posts || []
}
} catch (err) {
error({ statusCode: 500, message: '数据获取失败' })
}
},
// SEO头部信息
head() {
return {
title: 'Vue SSR演示应用',
meta: [
{
hid: 'description',
name: 'description',
content: '这是一个Vue.js服务端渲染演示应用,展示SSR的核心功能和性能优势。'
},
{
hid: 'keywords',
name: 'keywords',
content: 'Vue.js, SSR, Nuxt.js, 服务端渲染, 性能优化'
},
{
property: 'og:title',
content: 'Vue SSR演示应用'
},
{
property: 'og:description',
content: '体验Vue.js服务端渲染的强大功能'
},
{
property: 'og:type',
content: 'website'
}
],
link: [
{
rel: 'canonical',
href: 'https://example.com/'
}
]
}
},
data() {
return {
counter: 0,
form: {
name: '',
email: ''
},
// 性能指标
ssrRenderTime: 0,
firstContentfulPaint: 0,
timeToInteractive: 0,
hydrationTime: 0
}
},
mounted() {
// 客户端水合完成后执行
this.measurePerformance()
this.startPerformanceMonitoring()
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
},
async submitForm() {
try {
const response = await this.$axios.post('/api/contact', this.form)
if (response.status === 200) {
alert('表单提交成功!')
this.form = { name: '', email: '' }
}
} catch (error) {
alert('表单提交失败,请重试。')
}
},
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('zh-CN')
},
measurePerformance() {
// 测量性能指标
if ('performance' in window) {
const navigation = performance.getEntriesByType('navigation')[0]
// 模拟SSR渲染时间(实际应该从服务端传递)
this.ssrRenderTime = Math.round(Math.random() * 100 + 50)
// 首次内容绘制
const fcp = performance.getEntriesByName('first-contentful-paint')[0]
if (fcp) {
this.firstContentfulPaint = Math.round(fcp.startTime)
}
// 可交互时间(简化计算)
this.timeToInteractive = Math.round(navigation.loadEventEnd - navigation.navigationStart)
// 水合时间(从DOM加载完成到Vue接管)
this.hydrationTime = Math.round(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart)
}
},
startPerformanceMonitoring() {
// 持续监控性能
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
entries.forEach(entry => {
if (entry.name === 'first-contentful-paint') {
this.firstContentfulPaint = Math.round(entry.startTime)
}
})
})
observer.observe({ entryTypes: ['paint'] })
}
},
getTimelineWidth(segment) {
const total = this.timeToInteractive
if (total === 0) return 0
switch (segment) {
case 'ssr':
return (this.ssrRenderTime / total) * 100
case 'download':
return ((this.firstContentfulPaint - this.ssrRenderTime) / total) * 100
case 'hydration':
return (this.hydrationTime / total) * 100
case 'interactive':
return ((this.timeToInteractive - this.firstContentfulPaint) / total) * 100
default:
return 0
}
}
}
}
</script>
<style scoped>
.demo-section {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.ssr-content,
.client-content,
.performance-monitor {
margin-bottom: 40px;
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.post-card {
padding: 20px;
border: 1px solid #e9ecef;
border-radius: 8px;
transition: transform 0.2s, box-shadow 0.2s;
}
.post-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.post-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.post-header h3 {
margin: 0;
color: #333;
font-size: 18px;
}
.post-date {
font-size: 12px;
color: #666;
white-space: nowrap;
}
.post-excerpt {
color: #666;
line-height: 1.6;
margin-bottom: 16px;
}
.post-meta {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #888;
}
.interaction-demo {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
margin-top: 20px;
}
.counter-demo,
.form-demo {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.counter-display {
text-align: center;
margin-top: 16px;
}
.counter-value {
font-size: 48px;
font-weight: bold;
color: #007bff;
display: block;
margin-bottom: 16px;
}
.counter-controls {
display: flex;
justify-content: center;
gap: 12px;
}
.counter-btn {
width: 50px;
height: 50px;
border: none;
border-radius: 50%;
background: #007bff;
color: white;
font-size: 24px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
.counter-btn:hover {
background: #0056b3;
}
.demo-form {
margin-top: 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.form-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: #007bff;
}
.submit-btn {
width: 100%;
padding: 10px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s;
}
.submit-btn:hover {
background: #1e7e34;
}
.performance-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 30px;
}
.metric-card {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
text-align: center;
border-left: 4px solid #007bff;
}
.metric-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.metric-value {
font-size: 32px;
font-weight: bold;
color: #007bff;
margin-bottom: 8px;
}
.metric-description {
font-size: 12px;
color: #888;
line-height: 1.4;
}
.timeline-container {
margin-top: 16px;
}
.timeline-bar {
display: flex;
height: 40px;
border-radius: 4px;
overflow: hidden;
margin-bottom: 8px;
}
.timeline-segment {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.timeline-segment.ssr {
background: #dc3545;
}
.timeline-segment.download {
background: #ffc107;
}
.timeline-segment.hydration {
background: #17a2b8;
}
.timeline-segment.interactive {
background: #28a745;
}
.timeline-labels {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #666;
font-family: monospace;
}
</style>SSR性能优化涉及服务端渲染速度、缓存策略、资源优化等多个方面:
// nuxt.config.js - Nuxt.js性能优化配置
export default {
// 渲染模式
mode: 'universal',
target: 'server',
// 性能优化配置
render: {
// 启用HTTP/2推送
http2: {
push: true,
pushAssets: (req, res, publicPath, preloadFiles) => {
return preloadFiles
.filter(f => f.asType === 'script' && f.isInitial)
.map(f => `<${publicPath}${f.file}>; rel=preload; as=${f.asType}`)
}
},
// 资源提示
resourceHints: true,
// 压缩
compressor: require('compression')({ threshold: 0 }),
// 缓存控制
static: {
maxAge: 1000 * 60 * 60 * 24 * 7 // 7天
}
},
// 构建优化
build: {
// 分析包大小
analyze: process.env.NODE_ENV === 'development',
// 优化CSS
optimizeCSS: true,
// 提取CSS
extractCSS: true,
// 代码分割
splitChunks: {
layouts: true,
pages: true,
commons: true
},
// Webpack优化
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
// Babel配置
babel: {
presets({ isServer }) {
return [
[
require.resolve('@nuxt/babel-preset-app'),
{
buildTarget: isServer ? 'server' : 'client',
corejs: { version: 3 }
}
]
]
}
}
},
// 缓存策略
cache: {
max: 1000,
maxAge: 1000 * 60 * 15 // 15分钟
},
// 模块配置
modules: [
'@nuxtjs/axios',
'@nuxtjs/pwa',
'@nuxtjs/sitemap'
],
// PWA配置
pwa: {
workbox: {
runtimeCaching: [
{
urlPattern: '/api/.*',
handler: 'NetworkFirst',
strategyOptions: {
cacheName: 'api-cache',
cacheExpiration: {
maxEntries: 100,
maxAgeSeconds: 300
}
}
}
]
}
},
// 头部优化
head: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
],
link: [
// 预连接到外部域名
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://api.example.com' },
// DNS预解析
{ rel: 'dns-prefetch', href: 'https://cdn.example.com' }
]
}
}SSR性能优化核心策略:
💼 性能提示:合理使用缓存策略,避免过度缓存导致内容更新延迟;监控服务端渲染时间,优化数据获取逻辑
同构应用实现前后端代码共享,提升开发效率:
// server/api/posts.js - 服务端API实现
const express = require('express')
const router = express.Router()
// 模拟数据库
const posts = [
{
id: 1,
title: 'Vue.js 3.0 新特性详解',
excerpt: '深入了解Vue.js 3.0带来的Composition API、性能优化等重要特性...',
author: '张三',
views: 1250,
createdAt: '2024-01-15T10:30:00Z'
},
{
id: 2,
title: 'Nuxt.js SSR最佳实践',
excerpt: '掌握Nuxt.js服务端渲染的核心技术和性能优化策略...',
author: '李四',
views: 980,
createdAt: '2024-01-14T14:20:00Z'
},
{
id: 3,
title: '现代前端性能优化指南',
excerpt: '从代码分割到缓存策略,全面提升Web应用性能...',
author: '王五',
views: 1580,
createdAt: '2024-01-13T09:15:00Z'
}
]
// 获取文章列表
router.get('/posts', (req, res) => {
const { page = 1, limit = 10, category } = req.query
let filteredPosts = posts
// 分类过滤
if (category) {
filteredPosts = posts.filter(post => post.category === category)
}
// 分页
const startIndex = (page - 1) * limit
const endIndex = startIndex + parseInt(limit)
const paginatedPosts = filteredPosts.slice(startIndex, endIndex)
// 模拟网络延迟
setTimeout(() => {
res.json({
posts: paginatedPosts,
total: filteredPosts.length,
page: parseInt(page),
limit: parseInt(limit)
})
}, Math.random() * 100 + 50)
})
// 获取单篇文章
router.get('/posts/:id', (req, res) => {
const { id } = req.params
const post = posts.find(p => p.id === parseInt(id))
if (!post) {
return res.status(404).json({ error: '文章不存在' })
}
// 增加阅读量
post.views++
setTimeout(() => {
res.json(post)
}, Math.random() * 50 + 25)
})
// 联系表单提交
router.post('/contact', (req, res) => {
const { name, email, message } = req.body
// 验证数据
if (!name || !email) {
return res.status(400).json({ error: '姓名和邮箱为必填项' })
}
// 模拟保存到数据库
console.log('Contact form submitted:', { name, email, message })
setTimeout(() => {
res.json({ success: true, message: '表单提交成功' })
}, Math.random() * 200 + 100)
})
module.exports = router// plugins/ssr-cache.js - SSR缓存插件
class SSRCache {
constructor(options = {}) {
this.options = {
maxAge: 1000 * 60 * 15, // 15分钟
maxSize: 100, // 最大缓存条目数
enableCompression: true,
...options
}
this.cache = new Map()
this.accessTimes = new Map()
}
// 生成缓存键
generateKey(url, userAgent, acceptLanguage) {
const factors = [
url,
this.isMobile(userAgent) ? 'mobile' : 'desktop',
acceptLanguage?.split(',')[0] || 'en'
]
return factors.join('|')
}
// 检测移动设备
isMobile(userAgent) {
return /Mobile|Android|iPhone|iPad/.test(userAgent)
}
// 获取缓存
get(key) {
const entry = this.cache.get(key)
if (!entry) {
return null
}
// 检查过期
if (Date.now() - entry.timestamp > this.options.maxAge) {
this.cache.delete(key)
this.accessTimes.delete(key)
return null
}
// 更新访问时间
this.accessTimes.set(key, Date.now())
return entry.data
}
// 设置缓存
set(key, data) {
// 检查缓存大小限制
if (this.cache.size >= this.options.maxSize) {
this.evictLRU()
}
const entry = {
data,
timestamp: Date.now(),
size: JSON.stringify(data).length
}
this.cache.set(key, entry)
this.accessTimes.set(key, Date.now())
}
// LRU淘汰策略
evictLRU() {
let oldestKey = null
let oldestTime = Infinity
for (const [key, time] of this.accessTimes) {
if (time < oldestTime) {
oldestTime = time
oldestKey = key
}
}
if (oldestKey) {
this.cache.delete(oldestKey)
this.accessTimes.delete(oldestKey)
}
}
// 清空缓存
clear() {
this.cache.clear()
this.accessTimes.clear()
}
// 获取缓存统计
getStats() {
const totalSize = Array.from(this.cache.values())
.reduce((sum, entry) => sum + entry.size, 0)
return {
size: this.cache.size,
maxSize: this.options.maxSize,
totalSize,
averageSize: totalSize / this.cache.size || 0
}
}
}
// Nuxt.js服务端中间件
export default function(req, res, next) {
// 只缓存GET请求
if (req.method !== 'GET') {
return next()
}
// 跳过API请求
if (req.url.startsWith('/api/')) {
return next()
}
const cache = new SSRCache()
const cacheKey = cache.generateKey(
req.url,
req.headers['user-agent'],
req.headers['accept-language']
)
// 尝试从缓存获取
const cachedResponse = cache.get(cacheKey)
if (cachedResponse) {
res.setHeader('X-Cache', 'HIT')
res.setHeader('Content-Type', 'text/html; charset=utf-8')
return res.end(cachedResponse)
}
// 拦截响应
const originalEnd = res.end
res.end = function(chunk, encoding) {
if (res.statusCode === 200 && chunk) {
// 缓存成功响应
cache.set(cacheKey, chunk.toString())
res.setHeader('X-Cache', 'MISS')
}
originalEnd.call(this, chunk, encoding)
}
next()
}// plugins/ssr-performance.js - SSR性能监控
class SSRPerformanceMonitor {
constructor() {
this.metrics = {
renderTime: [],
memoryUsage: [],
cacheHitRate: 0,
errorRate: 0
}
this.startTime = Date.now()
}
// 记录渲染时间
recordRenderTime(startTime, endTime) {
const renderTime = endTime - startTime
this.metrics.renderTime.push({
time: renderTime,
timestamp: Date.now()
})
// 保持最近100条记录
if (this.metrics.renderTime.length > 100) {
this.metrics.renderTime.shift()
}
// 性能告警
if (renderTime > 1000) {
console.warn(`Slow SSR render: ${renderTime}ms`)
this.sendAlert('slow_render', { renderTime })
}
}
// 记录内存使用
recordMemoryUsage() {
const memUsage = process.memoryUsage()
this.metrics.memoryUsage.push({
...memUsage,
timestamp: Date.now()
})
// 保持最近50条记录
if (this.metrics.memoryUsage.length > 50) {
this.metrics.memoryUsage.shift()
}
// 内存告警
const heapUsedMB = memUsage.heapUsed / 1024 / 1024
if (heapUsedMB > 500) {
console.warn(`High memory usage: ${heapUsedMB.toFixed(2)}MB`)
this.sendAlert('high_memory', { heapUsedMB })
}
}
// 计算平均渲染时间
getAverageRenderTime() {
if (this.metrics.renderTime.length === 0) return 0
const total = this.metrics.renderTime.reduce((sum, item) => sum + item.time, 0)
return total / this.metrics.renderTime.length
}
// 获取性能报告
getPerformanceReport() {
const now = Date.now()
const uptime = now - this.startTime
return {
uptime,
averageRenderTime: this.getAverageRenderTime(),
memoryUsage: this.getCurrentMemoryUsage(),
cacheHitRate: this.metrics.cacheHitRate,
errorRate: this.metrics.errorRate,
recentRenderTimes: this.metrics.renderTime.slice(-10)
}
}
getCurrentMemoryUsage() {
const memUsage = process.memoryUsage()
return {
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024),
rss: Math.round(memUsage.rss / 1024 / 1024)
}
}
// 发送告警
sendAlert(type, data) {
// 发送到监控系统
if (process.env.MONITORING_WEBHOOK) {
const alert = {
type,
data,
timestamp: Date.now(),
server: process.env.SERVER_ID || 'unknown'
}
// 这里可以集成到实际的监控系统
console.log('Performance Alert:', alert)
}
}
// 启动定期监控
startMonitoring() {
// 每30秒记录一次内存使用
setInterval(() => {
this.recordMemoryUsage()
}, 30000)
// 每5分钟生成性能报告
setInterval(() => {
const report = this.getPerformanceReport()
console.log('SSR Performance Report:', report)
}, 300000)
}
}
// Nuxt.js插件
export default ({ app }, inject) => {
if (process.server) {
const monitor = new SSRPerformanceMonitor()
monitor.startMonitoring()
// 注入到上下文
inject('ssrMonitor', monitor)
// 监控渲染过程
app.router.beforeEach((to, from, next) => {
app.$ssrRenderStart = Date.now()
next()
})
app.router.afterEach(() => {
if (app.$ssrRenderStart) {
monitor.recordRenderTime(app.$ssrRenderStart, Date.now())
}
})
}
}# Dockerfile - SSR应用容器化部署
FROM node:16-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production && npm cache clean --force
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产环境镜像
FROM node:16-alpine AS production
# 创建应用用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nuxt -u 1001
# 设置工作目录
WORKDIR /app
# 复制构建产物
COPY --from=builder --chown=nuxt:nodejs /app/.nuxt ./.nuxt
COPY --from=builder --chown=nuxt:nodejs /app/static ./static
COPY --from=builder --chown=nuxt:nodejs /app/nuxt.config.js ./
COPY --from=builder --chown=nuxt:nodejs /app/package*.json ./
# 安装生产依赖
RUN npm ci --only=production && npm cache clean --force
# 切换到应用用户
USER nuxt
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# 启动应用
CMD ["npm", "start"]# docker-compose.yml - 完整部署配置
version: '3.8'
services:
# Nuxt.js应用
nuxt-app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NUXT_HOST=0.0.0.0
- NUXT_PORT=3000
- API_BASE_URL=http://api:8080
depends_on:
- redis
- api
volumes:
- ./logs:/app/logs
restart: unless-stopped
deploy:
replicas: 2
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Redis缓存
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
# API服务
api:
image: node:16-alpine
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
restart: unless-stopped
# Nginx负载均衡
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- nuxt-app
restart: unless-stopped
volumes:
redis_data:通过本节Vue SSR深度教程的学习,你已经掌握:
A: 当应用需要SEO优化、首屏性能要求高、内容驱动时使用SSR。SPA适合交互密集的应用,SSR适合内容展示类应用。
A: 是的,SSR需要服务器渲染HTML。通过合理的缓存策略、负载均衡和性能优化可以有效控制服务器负载。
A: 确保服务端和客户端渲染结果一致,避免在mounted钩子中修改DOM,使用process.client判断执行环境。
A: 结合WebSocket、Server-Sent Events或轮询技术实现实时更新,在客户端水合后建立实时连接。
A: 使用热重载、开发代理、错误边界等技术,建立完善的开发工具链和调试环境。
// ecosystem.config.js - PM2部署配置
module.exports = {
apps: [
{
name: 'nuxt-app',
script: './node_modules/nuxt/bin/nuxt.js',
args: 'start',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true,
max_memory_restart: '500M',
node_args: '--max-old-space-size=1024'
}
]
}"服务端渲染是现代Web开发的重要技术选择。通过合理的SSR架构设计、性能优化策略和运维实践,我们能够构建既有良好SEO效果又有优秀用户体验的Web应用。记住,SSR不是银弹,需要根据具体业务需求和技术场景做出合理选择!"