如何打造高性能且SEO友好的单页应用(SPA)

1a73ecdb082f212bf8d81eb9a3a53e29?s=47 Jecelyn Yeen
November 24, 2019

 如何打造高性能且SEO友好的单页应用(SPA)

1a73ecdb082f212bf8d81eb9a3a53e29?s=128

Jecelyn Yeen

November 24, 2019
Tweet

Transcript

  1. 如何打造 ⾼高性能 & SEO 友好 单⻚页应⽤用(SPA)

  2. #ngMY2019, 7⽉月6-7号 400+ 出席者, 30+ 国家

  3. @JecelynYeen (阮阮⻉贝琪) • 编码⼗十年年 • ⾕谷歌开发者专家(GDE) • 软件架构师(Architect) • 培训

    / 咨询 • NG-MY 发起⼈人
  4. ⽣生平第⼆二次⽤用中⽂文演讲, 赏个脸,多多包涵, 别打盹!

  5. @JecelynYeen ng-my.org

  6. @JecelynYeen ➔项⽬目背景 ➔SEO优化 ➔提⾼高性能

  7. 7 项⽬目背景

  8. @JecelynYeen 免付费的✌!

  9. 开发预算:¥0

  10. @JecelynYeen 单⻚页应⽤用 (SPA) Firebase 寄存 (Free) ⽆无数据库 (JSON) Start with

    Blank (Angular CLI)
  11. ng new ng-my --createApplication false ng generate application site2019 创建

    blank workspace 创建 application
  12. 12 SEO优化

  13. @JecelynYeen

  14. <title>July 06-07 | NG-MY 2019</title> <meta name="description" content="NG-MY 2019..."> <meta

    property="og:url" content="https://2019.ng-my.org/" /> <meta property="og:description" content="NG-MY 2019..."> <meta property="og:type" content="website" /> <meta property="og:title" content="..." /> <meta property="og:image" content="img.png" /> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:image" content="img.png"> Title tag & Meta tags are essential for SEO
  15. constructor(private title: Title, private meta: Meta) {} setPageMeta(title: string, metaDesc:

    string, metaImg: string) { this.title.setTitle(title); this.meta.updateTag({ property: 'og:title', content: title }); this.meta.updateTag({ name: 'description', content: metaDesc }); this.meta.updateTag({ name: 'twitter:image', content: metaImg }); ... } export class PageService { } Utilize the built-in Title & Meta Service Set page title Update meta tag Inject title & meta services
  16. Dev 开发环境 (No crawling) Prod ⽣生成环境 (Allow crawling) Robots.txt 协议

    - To crawl or not to crawl? robots.txt robots.prod.txt
  17. ... "architect": { "build": { "assets": [ ..., "projects/site2019/src/robots.txt" ]

    ... } } angular.json Include robots.txt as output assets
  18. "configurations": { "production": { "fileReplacements": [ ... { "replace": "projects/site2019/src/robots.txt",

    "with": "projects/site2019/src/robots.prod.txt" } ] } } Replace file in angular.json Environment name Replace when env = production ng build -c production, or ng build --prod
  19. @JecelynYeen 为啥还是 不不work呢!?!

  20. SEO Problem in SPA <html> <head> <script src="bundle.js"> </head> ...

    </html> 伺服器器 sends HTML to 浏览器器 (No meta tags) <html> <head> <title>home</title> <meta ...> <script src="bundle.js"> </head> ... </html> 浏览器器 executes JS and add title & meta tags (too late!) Most crawlers only understand this. :(
  21. @JecelynYeen Solution 构建时预渲染 Prerendering during build time

  22. Brief: Angular Application Build Process (CLI) ng build --prod Generate

    & Optimize files - js, css, etc Generate index.html Webpack Index transform We can extend & customize these!
  23. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Open browser Browse & Save each route as .html Deploy all files to server Generate static html pages = Prerendering Generate index.html
  24. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Deploy all files to server Automate Prerendering using Puppeteer Generate index.html Program Puppeteer Browse & Save each route as .html
  25. Puppeteer 前端神器器 - Programmable Chrome Browser const puppeteer = require('puppeteer');

    (async () => { })(); const fs = require('fs').promises; await fs.writeFile('food.html', html, 'utf-8'); await browser.close(); await page.goto('https://ng-my.org/food', { waitUntil: ‘networkidle0'}); const html = await page.content(); const browser = await puppeteer.launch(); const page = await browser.newPage();
  26. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Program Puppeteer Browse & Save each route as .html Deploy all files to server Generate index.html Extend Angular CLI to customize this step Find an even easier library to help us!
  27. Extend Angular CLI to handle Prerendering npm install -D @angular-builders/custom-webpack

    npm install -D prerender-xs Extend Angular CLI Library to prerender with Puppeteer https://github.com/just-jeb/angular-builders https://github.com/chybie/prerender-xs
  28. const Prerenderer = require('prerender-xs'); const routes = ['/food', '/team', '/speakers',

    ...]; index-html-transform.js Update angular.json to use this custom file module.exports = async (targetOptions, indexHtml) => { } const data = await Prerenderer.render({ staticDir: 'dist/site2019', routes, indexHtml }); return data.find(x => x.route === '/').html;
  29. Sitemap is good to have https://support.google.com/webmasters/answer/156184?hl=en

  30. Generate Sitemap Automatically ng build --prod Generate & Optimize files

    - js, css, etc Deploy all files to server Generate sitemap.xml Extend Angular CLI Webpack to do so Generate index.html
  31. Extend Angular CLI for Sitemap Generation npm install -D create-file-webpack

    https://github.com/Appius/create-file-webpack Webpack plugin to create file (any format)
  32. module.exports = { plugins: [ new CreateFileWebpack({ path: 'dist/site2019', fileName:

    'sitemap.xml', content: generateSitemap(routes) })] } const CreateFileWebpack = require('create-file-webpack'); const routes = ['/food', '/team', '/speakers', ...] extra-webpack.config.js Our custom method to generate sitemap
  33. 33 提⾼高性能

  34. 遇到⻳龟速⽹网⻚页的⽆无奈奈…

  35. @JecelynYeen Page Speed matters to SEO Ranking https://developers.google.com/web/updates/ 2018/07/search-ads-speed

  36. http://bit.ly/mobile-page-weight Page Weight (Mobile)

  37. @JecelynYeen ➔不不劳⽽而获* ➔图⽚片 ➔JavaScript ➔字体

  38. @JecelynYeen 不不劳⽽而获*

  39. @JecelynYeen ➔ 压缩丑化 Uglify (CLI) ➔ 事前编译 AOT (CLI) ➔

    GZIP (Firebase) ➔ 差异化加载Differential Loading (CLI) Performance Optimization we get for FREE! ng build --prod
  40. 差异化加载 能节省7-20% 的捆绑包⼤大⼩小 现代浏览器器 旧版浏览器器 Minimal polyfill Modern syntax Full

    polyfill ES5 syntax
  41. 缓存静态资源 Cache Static Assets

  42. { "hosting": { "headers": [{ "source": "**/*.@(js|css|jpg|jpeg|gif|png|webp|svg)", "headers": [{ "key":

    "Cache-Control", "value": "max-age=31536000" }] }], ... }, } Efficient cache (firebase.json) js, css, images longer cache
  43. @JecelynYeen 图⽚片优化

  44. 5MB Images a page 流量量套餐

  45. SVG

  46. 32kb

  47. @JecelynYeen NPM: https://www.npmjs.com/package/svgo UI: https://jakearchibald.github.io/svgomg/ Optimize SVG with SVGOMG 32kb

    -> 2kb (867B gzip)
  48. @JecelynYeen WebP Images are 25-35% smaller than equivalent JPEG 0r

    PNG.
  49. 78% Global User WebP has Arrived Based on data from

    caniuse Source: bit.ly/webp-support Supported Supported Supported
  50. .png - 119 kb .webp - 28 kb .png -

    278 kb .webp - 30 kb
  51. Serve WebP and support browsers <picture> <source srcset="拉茶.webp" type="image/webp"> <img

    src=“拉茶.png"> </picture> Image container If browser supports WebP Else PNG it is
  52. 图⽚片压缩 Lossy Image For most images, 80-85% quality will reduce

    file size by 30-40% with minimal effect on image quality.
  53. 495 kb 180 kb (80% quality) 图⽚片压缩 Lossy Image

  54. Image in desktop & tablet can be ~2-4x larger than

    mobile 28 kb 12 kb 响应式图⽚片 Responsive Images
  55. Serve different image sizes <picture> <source media="(max-width: 800px)" srcset=“拉茶⼩小.webp" type="image/webp">

    <source srcset="拉茶.webp" type="image/webp"> <img src="拉茶.png" > </picture> Small screen and if browser supports WebP https://developer.mozilla.org/en-US/docs/Learn/ HTML/Multimedia_and_embedding/ Responsive_images
  56. @JecelynYeen squoosh.app resize, compress, format Or automate with these npm

    packages: imagemin, sharp, jimp
  57. @JecelynYeen Native lazy loading Defer fetching offscreen images / iframes

    until a user scrolls near them. Load when scrolling down
  58. image / iframe lazy load <picture> ... <img src="teh-tarik.png" loading="lazy">

    </picture> <iframe src="https://www.youtube.com/embed/RYUPiv_lRFI" loading="lazy"> </iframe> https://developer.mozilla.org/en-US/docs/Learn/ HTML/Multimedia_and_embedding/ Add lazy loading attribute
  59. Performant Images ➔Appropriate format 格式 ➔Appropriate compression 压缩 ➔Appropriate display

    size 体积 ➔Is lazy 懒加载
  60. JavaScript

  61. 倘若能瞬间消灭 ⼀一半的js files...

  62. 延迟加载 Lazy Loading 旧式 SPA (Eager Load) - big-bundle.js (60kb)

    现代 SPA (Split & Lazy Load) - route-speakers.js (20kb) - route-food.js (20kb) - route-schedule.js (20kb) - ….
  63. Lazy Loading 1-2 pages per module const routes: Routes =

    [ { path: 'speakers', loadChildren: () => import('./speakers/speakers.module') .then(m => m.SpeakersModule) }, ... ]; Latest Angular version 8 syntax
  64. Defer JavaScript <script> <script defer> HTML parsing HTML parsing paused

    Script download Script execution
  65. Defer 3rd party libraries <script defer src="https://www.googletagmanager.com/gtag/js?id=xxx"> </script> <script> ...

    </script> Defer it Reduction of domComplete time
  66. @JecelynYeen 字体

  67. Display Font Immediately By default, if a font is not

    loaded, The browser will hide text for up to:
  68. Flash of Unstyled Text (FOUT) @font-face { font-family: Source Sans

    Pro', sans-serif; src: url('...') format('woff'); font-display: swap; } Display unstyled text until font loaded .5s improvement in “Visual Complete” on 3G
  69. fonts.googleapis.com/css? family=Source+Sans+Pro&display=swap

  70. icomoon.io 减低图标体积 13 icons - 3.7kb

  71. @JecelynYeen -猜猜⽹网⻚页最终体积-

  72. 343 kb 268 kb 结果 Result (ng-my.org)

  73. ⽹网站性能测试⼯工具 Lighthouse | Web Page Test | PageSpeed Insights

  74. ⽹网⻚页 (Web) ⽹网⻚页超能战⼠士 (We)

  75. Make the Web Better for E.V.E.R.Y.O.N.E

  76. 谢谢⼤大家!我有贴纸。 @JecelynYeen 推特 / 微信 github: github.com/chybie/ng-my slides: bit.ly/ng-my-site video:

    youtu.be/6l779_V4LV8 Instagram or FB me! @ngmykia