单页应用禁用页面刷新时,能否使用EJS实现服务端渲染?
解决方案:SPA无刷新导航与EJS的取舍
一、如何规避手动输入地址/location.assign导致的页面刷新?
完全可以实现,核心是前端路由拦截配合服务端 fallback 配置,让所有导航行为都在SPA内部处理,不触发浏览器刷新:
1. 服务端调整路由
- 将原页面路由改为API接口,返回动态HTML片段或数据,而非完整页面;
- 添加一个 catch-all 路由,所有非API请求都返回SPA的主入口页面(包含WebGL逻辑和前端路由)。
示例代码:
// 原/test路由改为API,返回EJS渲染的HTML片段 server.get('/api/test', (req, res) => { // 渲染仅包含动态内容的EJS片段(比如test-fragment.ejs) res.render('test-fragment', { /* 传入渲染所需数据 */ }, (err, html) => { res.send(html); }); }); // Catch-all路由:所有非API请求返回SPA主页面 server.get('*', (req, res) => { res.render('index'); // index.ejs是SPA入口,包含WebGL和前端路由逻辑 });
2. 前端拦截导航行为
- 拦截所有内部链接的点击事件,阻止默认跳转,用
history.pushState更新URL; - 监听
popstate事件,处理浏览器前进/后退; - 发起fetch请求获取动态内容,手动更新页面的指定容器,同时保留WebGL上下文。
示例代码:
// 拦截a标签点击,实现无刷新导航 document.addEventListener('click', (e) => { const link = e.target.closest('a'); if (link && link.href.startsWith(window.location.origin)) { e.preventDefault(); const path = new URL(link.href).pathname; // 更新URL而不刷新页面 history.pushState({ path }, '', path); // 加载对应页面内容 loadDynamicContent(path); } }); // 处理浏览器前进/后退 window.addEventListener('popstate', (e) => { if (e.state?.path) { loadDynamicContent(e.state.path); } }); // 加载并更新动态内容 async function loadDynamicContent(path) { // 请求对应API获取HTML片段 const res = await fetch(`/api${path}`); const html = await res.text(); // 更新页面中的动态内容容器 document.getElementById('dynamic-container').innerHTML = html; // 同步更新WebGL场景(根据路由调整) updateWebGLScene(path); }
这样处理后,无论是手动输入地址(服务端返回主页面,前端路由解析URL并加载内容),还是点击链接,都不会触发页面刷新,WebGL上下文始终保持活跃。
二、必须前端手动渲染时,是否需要放弃EJS?
不需要,可以根据需求选择两种方案:
1. 保留EJS做服务端片段渲染
继续用EJS在服务端渲染动态HTML片段(仅页面的可变部分,不含页面骨架),前端fetch后直接插入页面。这种方式可以利用EJS的模板语法在服务端处理数据,减少前端模板的开发量,适合数据逻辑更偏向服务端的场景。
2. 改用前端模板渲染
如果希望完全在前端做渲染,服务端只需返回JSON数据,前端使用模板字符串、Handlebars、Vue/React模板等方式渲染HTML。这种方式更符合前后端分离的SPA模式,适合交互复杂、需要前端控制渲染逻辑的场景。
内容的提问来源于stack exchange,提问作者Candleout
相关产品推荐
相关产品推荐

