How We Build NG-MY Website

How We Build NG-MY Website

NG-MY is a speedy SPA, SEO friendly website and built with Angular.

In this talk we will share some techniques we apply in building NG-MY Website, you can apply these techniques in your website too!
- Cost
- Collaboration
- Performance
- Techniques

1a73ecdb082f212bf8d81eb9a3a53e29?s=128

Jecelyn Yeen

July 06, 2019
Tweet

Transcript

  1. How We Build ng-my.org

  2. Don’t fall asleep yet!

  3. _Jenning Ho_ _Jecelyn Yeen_ _Adrian Yeong_

  4. None
  5. ➔ Background ➔ Search Engine Optimization (SEO) ➔ Collaboration ➔

    Performance ➔ Techniques
  6. 6 Background

  7. Free stuff!! I love that!

  8. Target: Cost $0

  9. Single Page App (SPA) Firebase Hosting (Free) NoDB (JSON) Start

    with Blank (Angular CLI)
  10. ng new --createApplication false ng generate application site2019 Create blank

    workspace Generate an application under the project
  11. ng add @angular/fire ng deploy site2019 Add deploy feature Build

    & deploy
  12. Rapid Prototyping with (free)

  13. 13 Search Engine Optimization

  14. None
  15. None
  16. <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
  17. export class PageService { } Utilize the built-in Title &

    Meta Service Set page title Update meta tag Inject title & meta services 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 }); ... }
  18. Dev (No crawling) Prod (Allow crawling) Robots.txt - To crawl

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

    ... } } angular.json Include robots.txt as output assets
  20. "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
  21. SPA & SEO Can we have both?

  22. Why is it not working!!!

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

    </html> Server sends HTML to Client (No meta tags) <html> <head> <title>home</title> <meta ...> <script src="bundle.js"> </head> ... </html> Client executes JS and add title & meta tags (too late!) Most crawlers only understand this. :(
  24. Solution Prerendering during build time

  25. Brief: Angular CLI Build Process ng build --prod Generate &

    Optimize files - js, css, etc Generate index.html Webpack Index transform We can extend & customize these!
  26. 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
  27. 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
  28. Puppeteer - Programmable Chrome Browser const puppeteer = require('puppeteer'); (async

    () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); })(); 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: 'networkidle2'}); const html = await page.content();
  29. 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!
  30. Extend Angular CLI Webpack 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
  31. const Prerenderer = require('prerender-xs'); const routes = ['/food', '/team', '/speakers',

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

  33. Solution: 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
  34. 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)
  35. const CreateFileWebpack = require('create-file-webpack'); const routes = ['/food', '/team', '/speakers',

    ...] module.exports = { plugins: [ new CreateFileWebpack({ path: 'dist/site2019', fileName: 'sitemap.xml', content: generateSitemap(routes) })] } extra-webpack.config.js Our custom method to generate sitemap https://github.com/chybie/ng-my/blob/master/ projects/site2019/extra-webpack.config.js
  36. Monitor Site SEO

  37. 37 Collaboration

  38. ➔ Work together & independently ➔ Mandatory Code Linting ➔

    Continuous Integration & Deployment (CI/CD)
  39. Flow Feature Branch Local Commit Trigger Linting During development... Automate

    with Husky
  40. Husky - Git Hook Made Easy npm install -D husky

    // package.json { "husky": { "hooks": { "pre-commit": "ng lint" } } }
  41. Flow Create PR PR Approved Merge to Master Branch Trigger

    Build Trigger Deployment Complete feature development Automate with Github actions (beta)
  42. .github/workflows/ main.yml name: CI on: push: branches: - master jobs:

    build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: 12.8 - run: npm install - run: npm run lint - run: npm run deploy env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} Only run in master Lint, Build & Deploy to Firebase
  43. 43 Performance

  44. Feeling of browsing slow website...

  45. Pleasant Browsing Experience

  46. Page Speed matters to SEO Page Ranking

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

  48. Measure Page Performance Lighthouse | PageSpeed Insights | Web Page

    Test
  49. ➔ Images ➔ Fonts ➔ Angular & JavaScript ➔ CSS

    ➔ Effortless
  50. Images

  51. 5MB Images a page Data Plan

  52. SVG

  53. 32kb

  54. NPM: https://www.npmjs.com/package/svgo UI: https://jakearchibald.github.io/svgomg/ Optimize SVG with SVGOMG 32kb ->

    2kb (867B gzip)
  55. Like PNG but smaller

  56. WebP Images are 25-35% smaller than equivalent JPEG 0r PNG.

  57. 78% Global User WebP Has arrived Based on data from

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

    278 kb .webp - 30 kb
  59. Serve WebP and support browsers <picture> <source srcset="teh-tarik.webp" type="image/webp"> <img

    src="teh-tarik.png"> </picture> Image container If browser supports WebP Else PNG it is
  60. Image Compression

  61. Lossy Image For most images, 80-85% quality will reduce file

    size by 30-40% with minimal effect on image quality
  62. 495 kb 180 kb (80% quality) Lossy Image

  63. Responsive Image

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

    mobile 28 kb 12 kb
  65. Serve different image sizes <picture> <source media="(max-width: 800px)" srcset="teh-tarik-800w.webp" type="image/webp">

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

    imagemin, sharp, jimp
  67. Native lazy loading Defer fetching offscreen images / iframes until

    a user scrolls near them. Load when scrolling down
  68. Performant Images ➔ Is Lazy ➔ Appropriate format ➔ Appropriate

    compression ➔ Appropriate display size
  69. Fonts

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

    loaded, The browser will hide text for up to:
  71. 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
  72. fonts.googleapis.com/css?family=Source+Sans+Pro &display=swap

  73. fonts.googleapis.com/css?family=Source+Sans+Pro :400,600,900 Limit font weights https://www.smashingmagazine.com/2019/06/o ptimizing-google-fonts-performance/

  74. Font import <head> <link href="https://fonts.googleapis..." rel="stylesheet"> </head> Eliminate render blocking

    above the fold <body> ... <style> @import "//fonts.googleapis..."; </style> </body> <head> <link href="https://fonts.googleapis..." rel="stylesheet"> </head>
  75. Icons

  76. If we are using only 10 icons, why should we

    load 100 other icons?
  77. icomoon.io 13 icons - 3.7kb

  78. Angular & JavaScript

  79. If only we could snap and destroy half the files...

  80. Lazy Loading Traditional SPA - big-bundle.js (60kb) Split & Lazy

    Load - route-speakers.js (20kb) - route-food.js (20kb) - route-schedule.js (20kb) - ….
  81. 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
  82. OnPush improves performance by minimizing change detection

  83. Change detection @Component({ selector: 'my-root', ... changeDetection: ChangeDetectionStrategy.OnPush }) export

    class AppComponent { } Change detection perform only when the component have received different inputs
  84. Defer JavaScript <script> <script defer> HTML parsing HTML parsing paused

    Script download Script execution
  85. Defer Google Tag Manager <script defer src="https://www.googletagmanager.com/gtag/js?id=xxx"> </script> <script> ...

    </script> Defer it Reduction of domComplete time
  86. CSS

  87. Minimize global css Move to component css

  88. Effortless

  89. Cache Static Assets

  90. { "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
  91. Performance Optimization we get for FREE! ➔ Minification & Uglify

    (CLI) ➔ Ahead of Time Compilation (CLI) ➔ GZIP (Firebase) ➔ Differential Loading (CLI) ng build --prod
  92. Smaller apps with Differential Loading Evergreen Legacy Minimal polyfill Modern

    syntax Full polyfill ES5 syntax
  93. -Guess the result!-

  94. 651 kb 469 kb Result

  95. 95 Techniques

  96. ➔ CMS Markdown ➔ Utilize ng-template ➔ Embed Google Forms

    ➔ CSS Variables ➔ CSS Grid
  97. CMS Markdown

  98. None
  99. npm install marked Markdown Parser

  100. this.selectedPost.content = marked.parse(result); const result = await fetch(`post-01.txt`) .then(res =>

    res.text()); ![picture](banner.jpg) Let’s make it happen LAH. Are you coming? 5 (+1) reasons why you should! ### 1. Enjoy incredible talks NG-MY provides you... post-page.component.ts post-01.txt
  101. Utilize ng-template

  102. None
  103. Repeated Day Schedule

  104. Should we extract it to a new child component?

  105. Not really, because it is only used within this component.

    We can use ng-template.
  106. <ng-template let-title="title" let-list="day" #schedule> <section> <h2>{{ title }}</h2> <div *ngFor="let

    schedule of list"> ... </div> </section> </ng-template> Input variables Template reference Create ng-template (schedule-page)
  107. <section> <ng-container [ngTemplateOutlet]="schedule" [ngTemplateOutletContext]="{ day: day1, title: 'Day 1' }">

    </ng-container> </section> Template reference Assign variable values Use ng-template (schedule-page) … <section> <ng-container [ngTemplateOutlet]="schedule" [ngTemplateOutletContext]="{ day: day2, title: 'Day 2' }"> </ng-container> </section> Reuse, pass in day 2 schedule
  108. Embed Google Forms

  109. iframe (responsive!)

  110. const form = ...; // from json const urlPattern =

    'https://docs.google.com/forms/d/e/{id} /viewform?embedded=true'; form.url = this.sanitizer.bypassSecurityTrustResou rceUrl(urlPattern.replace('{id}', form.id)); { "call-for-sponsors": { "name": "Call for Sponsors", "desc": "Interested to...", "id": "google-form-id", "height": 1633 }, ... } form-page.component.ts forms.json Assign to iframe
  111. CSS Variables

  112. Color fades in Color fades out Shadow increases

  113. You don’t even notice, DON’T YOU??!

  114. :root { --hero-scroll: 0; } global.css .menu { opacity: var(--hero-scroll);

    } menu.component.css .bg:before { opacity: calc(1 - 1 * var(--hero-scroll)); } .bg { opacity: calc(0.3 + 0.4 * var(--hero-scroll)); } home.component.css
  115. @HostListener('window:scroll', ['$event']) onScroll(e) { const opacityValue = ...; // calculation

    document.documentElement.style.setProperty( '--hero-scroll', `${opacityValue}` ); } menu.component.ts Update CSS Variables Listen to scroll
  116. Can I Use 91.11% IE Edge Firefox Chrome Safari 11

    18 67 75 12.1
  117. CSS Grid

  118. CSS Grid Collage

  119. Define grid template 5 columns, 12 rows size = 1

    col size = 2 cols size = 2 rows, 2 cols
  120. .container { display: grid; grid-template-columns: repeat(5,200px); grid-template-rows: repeat(12,200px); grid-auto-flow: row

    dense; } 5 columns 12 rows Auto placement algorithm No. of rows and columns different based on screen size (media queries)
  121. How auto placement algorithm works? dense = fill the holes

    https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow
  122. .photo:nth-child(1) { grid-row: span 2; grid-column: span 2; } Define

    how many rows & columns each photo take/
  123. These are CSS Grid as well!

  124. Can I Use 92.03% IE Edge Firefox Chrome Safari 11

    18 67 75 12.1
  125. None
  126. None
  127. Thank you! Follow @ngmykia (Instagram, Facebook) github: https://github.com/chybie/ng-my slides: https://bit.ly/ng-my-site

    video: https://youtu.be/6l779_V4LV8