Headless Chrome:服务端渲染JS站点的一个方案【上篇】【翻译】
原文链接:https://developers.google.com/web/tools/puppeteer/articles/ssr
注:由于英文水平有限,没有逐字翻译,可以选择直接阅读原文
tips:Headless浏览器完全可以作为服务端渲染的一个替代方案,在服务端转化js 站点为静态html页面;在webserver 上运行Headless 浏览器完全可以预渲染现代js 模式的应用,增加响应速度,对SEO也更加友好
本篇涉及到的技术展示了如何通过Google Headless 框架(puppteer)向一个Express web server 添加服务端渲染能力,对应用对友好的是,基本上不需要修改任何代码;所有的工作基本都有puppteer承担,通过简单的几行代码你就可以在服务端渲染几乎所有页面。
下面是将要涉及到的一小段代码:
import puppeteer from 'puppeteer';
async function ssr(url) {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto(url, {waitUntil: 'networkidle0'});
const html = await page.content(); // 页面的html内容
await browser.close();
return html;
}
注意:本篇文章代码基于es modules,需要node 8.5+ 并开启--experimental-modules
介绍
如果你需要seo,你登录进来阅读这篇文章无外乎两种原因:第一,你已创建了一个web 应用,但是它没有被搜索引擎索引到,你的应用可能是一个SPA、PWA应用。或者其实技术栈创建的应用,实际上你使用的技术栈也无关重要;重要的是,你花费了大量的时间创建了很棒应用,但是用户却无法发现它。第二,你可能是从其它网站注意到服务端渲染能提高一定的性能。你在这可以可以收获如何减少javascript 启动成本以及如何提高首屏渲染。
tips:一些框架如(Preact)已经支持服务端渲染了,如果你使用的框架有服务端渲染的解决方案,那么坚持使用就好了,没有必要引入一个新的工具。
爬取现代web应用
搜索引擎主要是爬取静态html标签来工作,但是现代的web 应用已经进化的比较复杂了。基于Javascript的应用,内容对网络爬虫来说是透明的,因为其内容多是在客户端通过js渲染的。一些爬虫比如google的爬虫也开始变得聪明了,google的爬虫使用Chrome41 执行Javascript 来得到最终页面,但是这种方案还是不太成熟、完美。比如,比如一些ES6的新特性在旧的浏览器中还是会引起Js error的。对于其他的搜索引擎,鬼知道他们怎么做的?O(∩_∩)O哈!
Headless Chrome 预渲染页面
所有爬虫都理解HTML,所以我们需要解决的是如何执行JS,来生成HTML。如果我告诉你有这样一个工具,你觉得如何?
- 这个工具知道如何运行所有类型的Javascript,然后产出静态的html
- 这个工具随着web添加新特性会持续更新
- 修改少量设置不需要修改任何代码,你可以快速把这个工具应用到已有应用之上
听起来很不错吧?这个工具就是浏览器!
Headless Chrome 不关心使用什么库、框架、或者工具链;它早饭吃进去Javascript,午饭就会吐出来静态的HTML。当然我们希望会比这个过程快很多--Eric
如果你使用Node,Puppteer是一种比较简单的方式来操作headless Chrome.它提供的API 是一个客户端应用支持服务端渲染功能。下面是一个简单的例子。
1.JS应用
我们以一个通过js动态生成HTML的动态页面的例子开始:
public/index.html
<html>
<body>
<div id="container">
<!-- Populated by the JS below. -->
</div>
</body>
<script>
function renderPosts(posts, container) {
const html = posts.reduce((html, post) => {
return `${html}
<li class="post">
<h2>${post.title}</h2>
<div class="summary">${post.summary}</div>
<p>${post.content}</p>
</li>`;
}, ''); // CAREFUL: assumes html is sanitized.
container.innerHTML = `<ul id="posts">${html}</ul>`;
} (async() => {
const container = document.querySelector('#container');
const posts = await fetch('/posts').then(resp => resp.json());
renderPosts(posts, container);
})();
</script>
</html>
2.SSR (Server Side Render)方法
接下来,简单实现一下ssr方法
ssr.mjs
import puppeteer from 'puppeteer'; //内存缓存,key:url value:html内容
const RENDER_CACHE = new Map(); async function ssr(url) {
if (RENDER_CACHE.has(url)) {
return {html: RENDER_CACHE.get(url), ttRenderMs: 0};
} const start = Date.now(); const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
// networkidle0 waits 500ms 没有其他请求时.
// The page's JS has likely produced markup by this point, but wait longer
// if your site lazy loads, etc.
await page.goto(url, {waitUntil: 'networkidle0'});
await page.waitForSelector('#posts'); //等待并确认 #posts 已经存在于dom中,如果已经存在,则立即执行.
} catch (err) {
console.error(err);
throw new Error('page.goto/waitForSelector timed out.');
} const html = await page.content(); // 被序列化后的HTML内容
await browser.close(); const ttRenderMs = Date.now() - start;
console.info(`Headless rendered page in: ${ttRenderMs}ms`); RENDER_CACHE.set(url, html); // cache rendered page. return {html, ttRenderMs};
} export {ssr as default};
主要代码逻辑:
- 添加缓存。缓存渲染后的HTML是提高响应的最有效方法,当你再次请求的时候,避免再次运行headless chrome。后续会讨论其他方面的优化。
- 对页面加载超时添加异常处理
- 调用
page.waitForSelector('#posts')方法,确保id为posts的元素在后续操作之前已经存在于DOM中(有多中waitForxxx方法) - 添加计量统计,计算Headless渲染页面时间
3.WebServer 端代码
最后,通过一个Express server 把所有内容联系到一起。哎直接看代码吧,代码中加了注释。
server.mjs
import express from 'express';
import ssr from './ssr.mjs'; const app = express(); app.get('/', async (req, res, next) => {
//调用上面写好的ssr方法,传入url,通过headless chrome 渲染完毕后把渲染结果返回
const {html, ttRenderMs} = await ssr(`${req.protocol}://${req.get('host')}/index.html`);
// Add Server-Timing! See https://w3c.github.io/server-timing/.
res.set('Server-Timing', `Prerender;dur=${ttRenderMs};desc="Headless render time (ms)"`);
return res.status(200).send(html); // Serve prerendered page as response.
}); app.listen(8080, () => console.log('Server started. Press Ctrl+C to quit'));
那么,得到的响应HTML应该是这样的:
<html>
<body>
<div id="container">
<ul id="posts">
<li class="post">
<h2>Title 1</h2>
<div class="summary">Summary 1</div>
<p>post content 1</p>
</li>
<li class="post">
<h2>Title 2</h2>
<div class="summary">Summary 2</div>
<p>post content 2</p>
</li>
...
</ul>
</div>
</body>
<script>
...
</script>
</html>
上篇结束,后续中篇 和 下篇 请继续关注
Headless Chrome:服务端渲染JS站点的一个方案【上篇】【翻译】的更多相关文章
- Headless Chrome:服务端渲染JS站点的一个方案【中篇】【翻译】
接上篇 防止重新渲染 其实说不对客户端代码做任何修改是忽悠人的.在我们的Express 应用中,通过Puppteer加载页面,提供给客户端响应,但是这个过程是有一些问题的. js脚本在服务端的Head ...
- vuejs服务端渲染更好的SEO,SSR完全指南Nuxt.js静态站生成器
vuejs服务端渲染更好的SEO,SSR完全指南Nuxt.js静态站生成器SSR 完全指南https://cn.vuejs.org/v2/guide/ssr.html在 2.3 发布后我们发布了一份完 ...
- Nuxt.js vue服务端渲染
一.为什么要用Nuxt.js 原因其实不用多说,就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题. 二.Nuxt.js和纯Vue项目的简单对比 1. build后目标产物不同 vue ...
- Vue.js 服务端渲染业务入门实践
作者:威威(沪江前端开发工程师) 本文原创,转载请注明作者及出处. 背景 最近, 产品同学一如往常笑嘻嘻的递来需求文档, 纵使内心万般拒绝, 身体倒是很诚实. 接过需求,好在需求不复杂, 简单构思 后 ...
- ASP.NET Core 与 Vue.js 服务端渲染
http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...
- 使用 PHP 来做 Vue.js 的 SSR 服务端渲染
对于客户端应用来说,服务端渲染是一个热门话题.然而不幸的是,这并不是一件容易的事,尤其是对于不用 Node.js 环境开发的人来说. 我发布了两个库让 PHP 从服务端渲染成为可能.spatie/se ...
- Vue 爬坑之路(十一)—— 基于 Nuxt.js 实现服务端渲染(SSR)
直接使用 Vue 构建前端单页面应用,页面源码时只有简单的几行 html,这并不利于网站的 SEO,这时候就需要服务端渲染 2016 年 10 月 25 日,zeit.co 背后的团队对外发布了一个 ...
- Vue.js与 ASP.NET Core 服务端渲染功能整合
http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...
- NET Core 与 Vue.js 服务端渲染
NET Core 与 Vue.js 服务端渲染 http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/原作者: ...
随机推荐
- 定位bug的姿势对吗?
举个例子来说明 WEB页面上数据显示错误,本来应该显示38, 结果显示35,这个时候你怎么去定位这个问题出在哪里? 1.通过fiddler抓包工具(或者其他抓包工具), 分析接口返回的数据是35还是 ...
- C#中的String类
一.String类的方法 1. Trim():清除字符串两端的空格 2. ToLower():将字符串转换为小写 3. Equals():比较两个字符串的值,bool 4. IndexOf(value ...
- CentOS 6.5 通过命令行安装发送邮件
1.安装sendmail: yum install sendmail 2.安装mailx: yum install mailx -y 3.编辑发送的配置文件: vi /etc/mail.rc #在最后 ...
- Docker深入浅出系列教程——Docker简介
我是架构师张飞洪,钻进浩瀚代码,十年有余,人不堪其累,吾不改其乐.如果你和我的看法不一样,请关注我的头条号,我们一起奇闻共赏,疑义相析. 本节属于入门简介,从三个小方面进行简单介绍Docker. Do ...
- 上海依图-电话面试-angularjs
树的遍历(树结构:node.name,node.children),输出node.name(递归) 指令的scope的绑定策略(@绑定DOM数学单向绑定:=双向数据绑定:&绑定父作用域函数) ...
- C语言作业--函数
一.PTA实验作业 题目1: 400-499 中4出现的次数 1. 本题PTA提交列表 2. 设计思路 一.main函数 1.函数声明int fun(int x) 2.定义变量i,k,i表示输入的值, ...
- 课堂测试ch06
课堂测试ch06 下面代码中,对数组x填充后,采用直接映射高速缓存,所有对x和y引用的命中率为(D) A. 1 B. 1/4 C. 1/2 D. 3/4 解析:在填充了之后,对于x和y数组,只有在引用 ...
- 201621123040《Java程序设计》第十周学习总结
1.本周学习总结 2.书面作业 2.1常用异常 2.1.1自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 算术异常ArithmeticException(除数为0的情况) 类 ...
- 201621123057 《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰 ...
- *.db-journal 是什么(android sqlite )数据库删除缓存
sqlite的官方文档,发现该文件是sqlite的一个临时的日志文件,主要用于sqlite数据库的事务回滚操作了.在事务开始时产生,在事务操作完毕时自动删除,当程序发生崩溃或一些意外情况让程序非法结束 ...