如何实现FastAPI与Vue 3的服务端渲染(SSR)?
FastAPI 与 Vue 3 SSR 实现方案及替代方案
一、FastAPI 实现 Vue 3 SSR 的可行方法
Vue 3 的 SSR 核心依赖 Node.js 环境执行组件渲染逻辑,FastAPI 作为 Python 框架无法直接运行 Vue 的 SSR 代码,但可以通过以下两种方式间接实现:
1. FastAPI 作为网关调用 Node.js SSR 服务
- 先基于 Node.js 搭建独立的 Vue SSR 服务:使用 Vue 官方的
@vitejs/plugin-vue-ssr或 Nuxt.js 构建 SSR 服务,暴露 HTTP 接口接收请求路径、动态数据,返回渲染后的完整 HTML。 - FastAPI 端通过
httpx等库调用该 Node 服务,将客户端请求的路径、需要注入的动态参数传递过去,拿到渲染后的 HTML 后返回给客户端。
示例代码(FastAPI 端):
from fastapi import FastAPI, Request, HTMLResponse import httpx app = FastAPI() NODE_SSR_URL = "http://localhost:3000/ssr" @app.get("/{path:path}") async def render_ssr(request: Request, path: str): async with httpx.AsyncClient() as client: response = await client.post(NODE_SSR_URL, json={ "path": path, "dynamic_data": {"user_id": request.headers.get("X-User-ID")} }) html = response.text return HTMLResponse(content=html, status_code=200)
2. Python 调用 Node 脚本执行 SSR 渲染
对于轻量场景,可通过 Python 的 subprocess 模块调用 Node 脚本,传入请求路径和动态数据,脚本执行 Vue SSR 渲染后输出 HTML,FastAPI 捕获输出并返回。
示例代码:
from fastapi import FastAPI, HTMLResponse import subprocess import json app = FastAPI() @app.get("/{path:path}") def render_ssr(path: str): params = json.dumps({"path": path, "data": {"title": "动态页面"}}) result = subprocess.run( ["node", "./vue-ssr-renderer.js", params], capture_output=True, text=True ) html = result.stdout return HTMLResponse(content=html)
二、Vue 构建产物与 Jinja2 结合的动态部署方案
如果不想依赖 Node.js SSR 服务,可以通过修改 Vue 分隔符、改造构建产物为 Jinja2 模板的方式实现动态页面,步骤如下:
1. 修改 Vue 模板分隔符
避免 Vue 的双大括号与 Jinja2 冲突,在 Vue 项目配置中修改分隔符:
- 若使用 Vite,在
vite.config.js中添加:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue({ template: { delimiters: ['[[', ']]'] } })] })
- 若使用 Vue CLI,在
vue.config.js中添加:
module.exports = { chainWebpack: config => { config.module .rule('vue') .use('vue-loader') .tap(options => { options.compilerOptions = { delimiters: ['[[', ']]'] } return options }) } }
2. 构建 Vue 项目并改造 index.html 为 Jinja2 模板
- 执行
npm run build生成dist目录。 - 将
dist/index.html移至 FastAPI 的templates目录,在需要动态插入数据的位置使用 Jinja2 语法,示例:
<head> <title>{{ page_title }}</title> <link rel="stylesheet" href="/assets/index.[hash].css"> </head> <body> <div id="app"></div> <script> window.__INITIAL_STATE__ = {{ initial_state | tojson }} </script> <script type="module" src="/assets/index.[hash].js"></script> </body>
3. FastAPI 配置路由与模板渲染
配置 FastAPI 加载静态文件、渲染模板,并处理 SPA 路由回退:
from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles import os app = FastAPI() static_folder = "./dist" templates = Jinja2Templates(directory="./templates") app.mount("/assets", StaticFiles(directory=f"{static_folder}/assets"), name="assets") @app.get("/") def render_home(request: Request): return templates.TemplateResponse("index.html", { "request": request, "page_title": "首页", "initial_state": {"username": "test_user"} }) @app.get("/{catchall:path}") def render_dynamic_page(request: Request, catchall: str): page_data = { "page_title": f"{catchall} 页面", "initial_state": {"path": catchall} } static_file_path = os.path.join(static_folder, catchall) if os.path.exists(static_file_path) and os.path.isfile(static_file_path): return StaticFiles(directory=static_folder).get_response(catchall, request.scope) return templates.TemplateResponse("index.html", { "request": request, **page_data })
4. Vue 读取全局初始化数据
在 Vue 入口文件中读取 FastAPI 注入的动态数据:
import { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.config.globalProperties.$initialState = window.__INITIAL_STATE__ app.use(router).mount('#app')
内容的提问来源于stack exchange,提问作者Vivek Joshy
相关产品推荐
相关产品推荐

