同构or服务端渲染

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

同构

同构(isomorph)这个概念应该来源于数学。同构是在数学对象之间定义的一类映射,若两个数学结构之间存在同构映射,那么这两个结构叫做“是同构的”。如果两个结构是同构的,那么其上的对象会有相似的属性和操作,对某个结构成立的命题在另一个结构上也就成立。

在大前端,同构JavaScript应用指的是,用JavaScript编写的应用能够同时运行于客户端和服务器。因此,只需编写一次代码,在服务器上执行它来渲染静态页面,同时执行于客户端以允许快速的交互。

大前端演变的过程:

纯后端渲染 -> 单页面应用(前端渲染和交互) -> 同构(后端渲染和前端交互)

现在,同构和服务端渲染(server side render, SSR)基本是同义词。

SSR的优缺点

优点:

  • 更好的SEO
  • 更快的页面呈现速度

缺点:

  • 更复杂的开发,开发的代码需要兼容前后端的runtime
  • 更复杂的构建和部署
  • 加重服务器负载

同构会不会使前后端的代码耦合在一起?答案是不会。同构只是同构SPA代码,提供API的后台代码完全可以独立成一个项目,可以和同构代码一起部署,也可以分开部署。分开部署的缺点是会多一次http请求,一个客户端请求会先请求同构的后端代码,而同构的后端代码又会请求提供API的应用来获取渲染需要的数据。如果同构的后端代码和供API的代码部署在同一台服务器,多出来的这一次请求基本不会影响性能。也可以一起部署,比如提供API的应用是用node编写的,只用把同构的后端代码的render部分插入到提供API的应用的listen部分就好了。

相关框架

基于React的SSR框架Next.js现在有19k的star,发展的比较好。而基于Vue的SSR框架Nuxt.js只有200多个star,比较惨淡。

看了一下Nuxt.js,写了简单的demo。Nuxt.js其实做了很多工作,主要是在vue.js的内核外又包了一层SSR相关的东西。个人觉得主要的问题有两个。第一是把现有的使用vue的项目迁移到Nuxt.js会很麻烦;第二是Nuxt.js把很多配置(比如webpack的配置)都封装起来了,极大地简化了项目的配置,但是带来的问题是,当项目需要非大众化的配置时,根本就无从下手,因为找不到对应的配置文件。

除了Nuxt.js,vue有一个插件vue-server-renderer来实现SSR。

vue-server-renderer

一个简单的项目文件结构:

├──src
│   ├── components
│   │   ├── Foo.vue
│   │   ├── Bar.vue
│   │   └── Baz.vue
│   ├── App.vue
│   ├── app.js # universal entry
│   ├── entry-client.js # runs in browser only
│   ├── entry-server.js # runs on server only
│   └── index.template.html
├──server.js

app.js是前后端共同的入口,一般返回vue实例的工厂函数。不能像SPA那样直接创建vue实例,因为直接创建的vue实例在服务端会被所有请求复用,从而造成状态污染。

import Vue from 'vue'
import App from './App.vue'

export function createApp () {
  const app = new Vue({
    render: h => h(App)
  })
  return { app }
}

entry-client.js是浏览器端的入口,创建vue实例,并挂载到DOM上。

import { createApp } from './app'

const { app } = createApp()

app.$mount('#app')

entry-server.js主要export一个供服务端的renderer使用的函数。

import { createApp } from './app'

export default context => {
  const { app } = createApp()
  return app
}

注意上面的import并不能在服务端运行,所以要用webpack进行打包。

index.template.html是首页渲染的模板文件,服务端渲染app得到的页面会替换掉<!–vue-ssr-outlet–>。注意这里没有id等于app的元素,而浏览器hydrate的挂载点是’#app’元素,这个元素将会由服务端渲染出的页面给出。

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>{{ title }}</title>
    <link rel="manifest" href="/manifest.json">
    <style>
      #skip a {  position:absolute;  left:-10000px;  top:auto;  width:1px;  height:1px;  overflow:hidden;  }
      #skip a:focus {  position:static;  width:auto;  height:auto;  }
    </style>
  </head>
  <body>
  <p>lance</p>
  <div id="skip"><a href="#app">skip to content</a></div>
  <!--vue-ssr-outlet-->
  </body>
</html>

server.js获取构建好的相关文件,然后处理请求:

// server.js
const createApp = require('/path/to/built-server-bundle.js')

server.get('*', (req, res) => {
  const context = { url: req.url }

  createApp(context).then(app => {
    renderer.renderToString(app, (err, html) => {
      if (err) {
        if (err.code === 404) {
          res.status(404).end('Page not found')
        } else {
          res.status(500).end('Internal Server Error')
        }
      } else {
        res.end(html)
      }
    })
  })
})

vue-hackernews-2.0

vue-hackernews-2.0是官方给出的一个SSR Demo。然而这个拥有6k多个star的demo跑不起来。调试了好久定位到了问题。问题出在demo获取数据的api是托管在firebase上的。

Firebase是一家实时后端数据库创业公司,它能帮助开发者很快的写出Web端和移动端的应用。Firebase在2014年10月被Google收购后,就和Google的云服务结合比较紧密。

由于在国内不翻墙是没办法访问google的网站的,所以获取数据的api访问不了。demo就卡在下面这段代码这里了:

// src/api/create-api-server.js
import Firebase from 'firebase'

export function createAPI ({ config, version }) {
  let api
  if (process.__API__) {
    api = process.__API__
  } else {
    Firebase.initializeApp(config)
    api = process.__API__ = Firebase.database().ref(version)

    //卡在下面
    ;['top', 'new', 'show', 'ask', 'job'].forEach(type => {
      api.child(`${type}stories`).on('value', snapshot => {
        api.cachedIds[type] = snapshot.val()
      })
    })
  }
  return api
}

当api访问不了时,在服务器端和浏览器端都没有输出任何错误提示,这应该算一个bug。由于对Firebase的用法不熟,所以暂时没修复这个bug。

如果要使demo跑起来,就要改写或干掉所有asyncData函数(asyncData是一个ssr获取异步数据的钩子函数,所有获取异步数据的逻辑都写在里面)。

在干掉asyncData函数后,来看看demo的运行结果。为了更好地分析build文件,不要使用npm run dev,因为里面加了很多调试功能相关的代码。直接运行 npm run build。会调用webpack –config build/webpack.client.config.js和webpack –config build/webpack.server.config.js分别对客户端和服务端的代码进行打包。客户端代码打包的文件为:

  • app.403a9442d21cef255283.js
  • manifest.9ceee00d01801404ed42.js
  • vue-ssr-client-manifest.json

服务端打包的文件如下:

  • vue-ssr-server-bundle.json

可以看到,服务端打包的文件只有一个json文件,并没有重复打包应用的js文件。因为应用的js文件是同构的,所以后端也能用,不用重复打包。vue-ssr-server-bundle.json用来创建服务端的renderer。renderer会为每个请求渲染出一个静态页面。

返回的静态页面示例:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Vue HN 2.0</title>
    <meta charset="utf-8">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <link rel="apple-touch-icon" sizes="120x120" href="/public/logo-120.png">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
    <link rel="shortcut icon" sizes="48x48" href="/public/logo-48.png">
    <meta name="theme-color" content="#f60">
    <link rel="manifest" href="/manifest.json">
    <style>
      #skip a {  position:absolute;  left:-10000px;  top:auto;  width:1px;  height:1px;  overflow:hidden;  }
      #skip a:focus {  position:static;  width:auto;  height:auto;  }
    </style>
  </head>
  <body>
  <p>lance</p>
  <div id="skip"><a href="#app">skip to content</a></div>
  <div id="app" data-server-rendered="true">
    <header class="header">
        <nav class="inner">
            <a href="https://github.com/vuejs/vue-hackernews-2.0" target="_blank" rel="noopener" class="github">Built with Vue.js</a>
        </nav>
    </header>
    <p>hello world !</p>
   </div>
   <script>window.__INITIAL_STATE__={"activeType":null,"itemsPerPage":20,"items":{},"users":{},"lists":{"top":[],"new":[],"show":[],"ask":[],"job":[]},"route":{"path":"\u002Ftop","hash":"","query":{},"params":{},"fullPath":"\u002Ftop","meta":{},"from":{"name":null,"path":"\u002F","hash":"","query":{},"params":{},"fullPath":"\u002F","meta":{}}}};(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());
   </script>
  </body>
</html>

注意上面的data-server-rendered=”true”属性将告诉客户端以hydrate模式渲染页面。window.INITIAL_STATE是后端渲染时注入的状态,避免前端重复获取。

使用SSR的Vue的运行流程

代码在部署后端,当有一个请求过来时,服务器会新建一个vue实例,渲染(render)出需要显示的页面的html,把得到的页面以字符串的形式返回给客户端。同时把相关的js文件也返回(首次请求时返回vue的runtime、webpack的runtime和app.js等文件,非首次请求返回按需加载的js文件),返回的js文件和单页面应用(SPA)返回的差不多。浏览器接收到这些文件后,通过js文件把静态页面的字符串hydrate成可以交互的应用。个人理解,这里的hydrate的工作包括处理事件、运行生命周期钩子函数、实现视图和数据的双向绑定等。

和SPA相比,SSR返回的数据就是多了个静态页面(字符串形式)。

大前端学习笔记 -- 搭建自己的服务器端渲染 (SSR) 搭建自己的SSR 一、渲染一个Vue实例 mkdir vue-ssr cd vue-ssr npm init -y npm i vue vue-server-renderder server.js const Vue = require('vue') const renderer = require('vue-server-renderer').createRenderer() const app = new Vue({ template: ` <div id="app 阅读详情

相关推荐

怎样实现服务器端渲染

服务端渲染的实现,通常有3种方式,第1种是手动进行项目的简单搭建,第2种是使用vue CLI 3脚手架进行搭建,第3种利用一些成熟框架来搭建(如Nuxt,js),本节讲解第1种和第2种方式,带领读者实现简单的服务器端渲染。 1.创建 vue-ssr项目 在C:\vue\chapter08目录中,使用命令行工具创建一个vue-ssr项目,具体命令如下: mkdir vue-ssr cd vue-ssr npm init -y 执行上述命令后,会在vue-ssr 目录下生成一个package.json文

CZ-001的博客 2298

关于vue服务端渲染 1 构建配置

服务端渲染的本质是后端根据路由的情况在后端渲染这个组件的html代码,然后发送到前端,前端通过传来的 data-server-rendered=”true”标识符来得知这个html是由服务器渲染的,然后进行加载到自身进行管理。 在服务端渲染的时候,beforeCreate和created生命周期都是存在于服务端的,没有任何浏览器对象,如果在里面访问document或者window这种对象会在渲染...

HeyWeoil的博客 1828

NextAuth+NestJS+Sentry+Vercel全链路工程化落地实战

现代Web应用从Demo走向生产环境,核心在于安全认证、可观测性与自动化部署三大支柱。JWT无状态鉴权机制替代UUID会话,结合NextAuth前端流程与NestJS服务端鉴权,构建可审计身份体系;Sentry通过client/server双端分离初始化与异常分级上报,实现精准错误监控;Vercel预览部署配合GitHub Actions与Slack通知,打通PR即交付的CI/CD闭环。这些并非孤立技术点,而是工程化落地中高频复现的‘最后一公里’问题——尤其在TypeScript全栈项目(如fp-ts教学平

dengyulong的专栏 511

vue服务端渲染

vue 服务端渲染

qq_42880714的博客 1571

搭建自己的 服务端渲染 SSR

··是占位符,为了接收将来要渲染的变量,不能写错,不能有多余的空格

woyebuzhidao321的博客 9512

Nuxt 报错:render function or template not defined in component: anonymous

问题 在 dev hot reload 模式下开发,本来是好的,添加了一段代码之后出现了这个错误。 详细的错误信息如下: 这提示看起来是系统级的错误,没什么头绪。于是google之,是找到挺多有提到这个错误的 issue,然而……都没看到有解决办法。 好吧,还是靠自己先看一下。 于是撤销大法好。。。 我先注释掉添加的那部分代码,保存,果然没有再报错了。 那就看看这部分添加的代码咯~ 分析&amp...

Dandelion_drq的专栏 9325

Flipper Zero Unleashed 固件 JavaScript 模块系统:使用 require() 加载 FAL 模块的完整指南

本文围绕 Flipper Zero Unleashed 固件内置的 JavaScript 脚本引擎,系统讲解其**外部 JS 模块(JS modules)机制**:模块以 C/C++ 编写、编译为 FAL 文件存放在 microSD 卡上,脚本通过 `require()` 函数按需加载。读完本文,你将掌握模块的导入方式与生命周期、`require()` 的底层加载原理(内置模块与外部 FAL 插件

gitblog_00116的博客 322

react生命周期的基本用法

写react也快半年了,讲一下自己对于生命周期的理解及各个生命周期的作用 首先,看一下一个组件的构造 import React,{ Component } from 'react'; class Demo extends Component { constructor(props,context) { super(props,context) this.stat...

beichen3997的博客 145

newforms FormSet 完全指南:动态多表单增删与删除标记的终极方案

**newforms FormSet** 是 React 同构表单库 newforms 中的多表单管理方案,让你在同一个页面里轻松实现"动态添加、删除、排序多个表单",并通过 **DELETE 删除标记** 安全地标记待删除数据。无论你是做地址簿、购物车还是批量资料录入,这篇 FormSet 完全指南都能帮你快速上手。 ## 为什么需要 FormSet?🧩 想象这样一个场景:用户要一次性填写

gitblog_00445的博客 84

Next.js学习记录

Next.js是一个基于React全栈框架,由Vercel开发和维护,那么它有什么优势呢?SSR服务端渲染,可以提高页面加载速度(现在会SSR的人才并不多,可以提升你的竞争力)SSG:静态站点生成,可以生成静态页面,类似于Vitepress / Astro等静态站点生成工具SEO: Next.js提供了SEO优化,让你的网站更容易被搜索引擎收录AI: Vercel提供了 AI SDK 可以跟Next.js轻松结合,让你可以轻松实现AI应用服务端操作。

m0_69067003的博客 216

Angular 11 + Scully:面向内容型站点的 Jamstack 静态生成实践

静态站点生成(SSG)是 Jamstack 架构的核心能力,其原理是在构建阶段预渲染所有页面为纯 HTML,从而消除运行时服务端依赖,大幅提升首屏性能、SEO 可见性与 CDN 缓存效率。相比服务端渲染SSR)的运维复杂度和客户端渲染(CSR)的 SEO 风险,SSG 特别适用于更新频率低、以内容展示为主的应用场景,如技术博客、产品官网与文档中心。Angular 11 首次通过路由配置公开化、Ivy 编译器签名稳定及 HTTP 拦截增强,为 SSG 工具(如 Scully)提供了原生级支持,使 Angul

csxc65837的博客 538

如何在SSR架构中实现离线可用?(一)

本系列文章将以一个实际项目作为研究对象,探讨离线可用这个 PWA 的重要特性在 SSR 架构中的应用思路。最后结合 Vue SSR 进行实际应用。 本文作为第一部分,以 PWA-Directory__ 为例。这是一个陈列 PWA 项目的站点,同时展示项目 Lighthouse 分数及其他页面性能数据。 在下一 Part 中,我们将顺着这一思路,结合 Vue SSR 在项目中进行实际应用(关注Ope...

weixin_34267123的博客 300

.NET技术博客的深度写作范式与静态基建实践

技术博客本质上是知识结晶的静态载体,其核心价值在于可信、可验证、可复现。从.NET生态出发,理解编译器原理与运行时机制是构建深度技术内容的基础;而将C#语言本质、Roslyn编译器、JIT优化等底层原理转化为可调试、可测量、可交互的文档形态,则体现了工程化写作的技术价值。这种范式超越了API教程与碎片化笔记,服务于中高级开发者对‘为什么这样设计’的系统性追问,广泛应用于性能调优、源码阅读、跨平台迁移等真实工程场景。老赵点滴正是这一理念的长期实践样本。

diedangxiang4092的博客 507

5分钟搭建复古像素风AI聊天终端:基于Nanbeige 4.1-3B与Vue/FastAPI

大语言模型(LLM)的应用落地,不仅关乎模型本身的推理能力,更在于如何通过前端工程化技术,为用户提供沉浸式的交互体验。其核心原理在于将模型API与精心设计的用户界面(UI)解耦,后端负责高效推理,前端则专注于视觉呈现与交互逻辑。这种架构的价值在于,开发者能以较低成本,快速为AI能力披上风格化的“外衣”,从而拓展其在游戏化对话、创意工具等场景的应用。本文以复古像素冒险游戏风格为例,详细介绍了如何利用Vue.js、CSS像素艺术与FastAPI,将Nanbeige 4.1-3B模型集成到一个充满情怀的聊天界面中

weixin_33928137的博客 284

Ubuntu 20.04 VNC全功能配置:音频/中文输入/多用户并发实战指南

VNC远程桌面不仅是画面传输,更是完整桌面会话的延伸。在Linux系统中,VNC能否支持音频播放、中文输入法、多用户隔离等能力,取决于其与X11会话管理、PulseAudio音频子系统及IBus/Fcitx输入法框架的深度集成。Ubuntu 20.04因默认启用GNOME 3.36+与Wayland兼容模式,导致传统tightvncserver等工具无法继承本地会话环境,引发‘有画面无声音’‘鼠标异常’‘搜狗输入法失效’等典型问题。TigerVNC凭借对Xtigervnc服务、systemd用户会话、Pul

weixin_34326558的博客 382

DeepSeek-V4预览版深度实测:长上下文与结构化输出实战指南

大语言模型的长上下文理解能力与结构化输出稳定性,是金融、法律、医疗等高精度场景落地的核心瓶颈。其技术原理涉及注意力机制优化、MoE动态稀疏激活及JSON Schema原生解码约束,显著提升跨段落语义一致性与字段级准确率。这类能力直接支撑研报生成、合同比对、临床指南解析等强逻辑任务,降低人工校验成本并加速业务闭环。本文基于DeepSeek-V4预览版的真实部署经验,聚焦128K上下文处理、FP16精度权衡、temperature=0.3确定性控制及vLLM+FastAPI生产封装等关键实践,为开发者提供可复现

chuliaoqiao4046的博客 470

用网络图建模井字棋:一种可解释、可预计算的AI决策方法

Network Graph Data Modeling 是一种将离散状态空间显式结构化为节点与边关系的数据建模范式,其核心原理是将系统所有合法状态作为节点、状态转移作为边,通过预计算赋予属性语义。相比传统递归博弈树,它以空间换时间,实现毫秒级确定性查询,并天然支持可视化、可审计与可解释性。该方法在有限状态游戏如 Tic Tac Toe 中尤为高效,不仅规避了Minimax算法的运行时开销与黑箱缺陷,更成为教学、产品集成与轻量级AI落地的理想载体——本文即以井字棋为标尺,完整呈现一张‘能呼吸’的决策网络如何从

weixin_30239339的博客 320

【信息科学与工程学】计算机科学与自动化 第二百三十三篇 凤凰架构01

编号类型领域系统软件/硬件或系统集成方案及系统架构模块子模块函数名称/算法名称算法的数学分析及计算机体系架构及数据结构与算法详细设计及参数列表及数值范围设计关联知识1共识算法分布式基石状态机复制架构:Proposer/Acceptor/Learner 多节点角色模型,基于操作转移(Operation Transfer)分布式共识PaxosPaxos 协议(Prepare/Promise/Accept/Accepted 两阶段)数学基础:多数派(Quorum)投票,基于鸽巢原理保证 W+R>N 时读写集合必有

weixin_49199313的博客 196

大厂测试工程师校招笔试核心考点解析与备考策略

软件测试是保障产品质量的关键环节,测试工程师不仅需要掌握功能验证方法,更要具备系统拆解与风险识别能力。从测试用例设计到计算机网络、操作系统等基础知识,都是校招笔试中的高频考察点。随着互联网技术演进,大厂校招笔试题量庞大、覆盖面广,意在筛选逻辑清晰、基础扎实的候选人。通过分析典型的测试工程师笔试卷,可以看出面试官对TCP/IP、数据库索引、死锁等底层原理的重视,以及对场景设计和边界处理能力的考察。理解这些考核逻辑,有助于求职者更有针对性地准备笔试,将理论知识与工程实践相结合,在竞争激烈的校招中脱颖而出。

weixin_28839549的博客 282
上一篇: eggjs源码分析
麦草zZ
博客等级 码龄9年 6粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值