Optimize like your life depends on it!

👣: there will be some things I won't cover

🐰🐰

Let's begin with the big question...

What's inside a black hole?

Is teleportation possible?

Why should we optimize?

It's part of our duty as programmers... 👨‍💻

Mindset

When upgrading an entire app...

Measure, Optimize, Monitor

📏 Measure

Lighthouse

3/100

performance

+15s

load time

2.6M

javascript

300Kb

images

Minify JS & CSS

Already done

👨‍🔬 Metrics

🔃 Fast load

First Paint

First Contentful Paint

First Meaningful Paint

Page load

😡

🐘 Small footprint

📈 Optimize

#1

Minify the HTML

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = () => ({
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname, 'index.html'),
            filename: 'index.html',
            inject: "body",
            minify: {
                html5: true,
                collapseWhitespace: true,
                minifyCSS: true,
                minifyJS: true,
                minifyURLs: false,
                removeComments: true,
            }
        })
    ]
});
#2

CSS

⚪ one           file per component/page

⚪ the twice rule!

Don't wanna have 1 gigantic CSS for the whole app...

#3

Images & Videos

R

Responsive

L

Lazy Load

NgF

NextGen Formats

1) Responsive Images & Videos

<picture>
    <source 
        srcset="share_high.jpg" 
        type="image/jpeg" media="(min-width: 1000px)">

    <source 
        srcset="share_low.jpg" 
        type="image/jpeg" media="(min-width: 600px)">
    
    <img src="share_very_low".jpg alt="people talking">
</picture>

2) Lazyload

<picture>
    <source 
        class="lazyload"
        data-srcset="share_high.jpg" 
        type="image/jpeg" media="(min-width: 1000px)">

    <source 
        class="lazyload"
        data-srcset="share_low.jpg" 
        type="image/jpeg" media="(min-width: 600px)">
    
    <img 
        class="lazyload" 
        data-src="share_very_low".jpg alt="people talking">
</picture>
import "lazysizes";
<picture>
    <source 
        class="lazyload"
        data-srcset="share_high.jpg" 
        type="image/jpeg" media="(min-width: 1000px)">

    <source 
        class="lazyload"
        data-srcset="share_low.jpg" 
        type="image/jpeg" media="(min-width: 600px)">

    <noscript>
        <img src="share_med.jpg" alt="people talking">
    </noscript>
    
    <img 
        class="lazyload" 
        data-src="share_very_low".jpg alt="people talking">
</picture>

What if there's no JS on the page though?

3) NextGen Formats

<picture>
    <source 
        class="lazyload"
        data-srcset="share.webp" 
        type="image/webp">

    <noscritp>
        <!-- ... -->
    </noscript>
    
    <img 
        class="lazyload" 
        data-src="share".jpg alt="people talking">
</picture>
const WebpImagesPlugin = require('imagemin-webp-webpack-plugin')

module.exports = () => ({
    plugins: [new WebpImagesPlugin ()]
});

MP4 < GIF

#4

Fonts

<head>
   <link 
        href="https://fonts.googleapis.com/css?family=Comfortaa:400,700" 
        rel="stylesheet"> 
   
    <link 
        href="https://fonts.googleapis.com/css?family=Lato:400,700" 
        rel="stylesheet"> 
    <link href="style.css" href="stylesheet">
</head>
body {
    font-family: 'Lato', sans-serif;
}
GET: index.html
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 400;
  src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v14/S6uyw4BMUTPHjx4wXg.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
❗ Oh! The body's using the font!

Show text!

GET: 
https://fonts.googleapis.com/...
GET:
style.css

👷‍♂️

GET: 
[].woff2

VS

<link 
    rel="preload" 
    as="font" 
    href="/dist/assets/fonts/comfortaa-v12-latin-regular.woff2" 
    type="font/woff2"
    crossorigin="anonymous" />

Self host fonts so that you can:

 

✅ preload them

✅ long-term cache them

#5

Compression & caching

📄

📦

gzip
JS code
"archived"
compress
===
const express = require('express');
const compression = require('compression');

const app = express();

app.use(compression());
app.use('/dist', express.static(`${__dirname}/dist`, {
    maxAge: 31536000
}));

Check our progress!

10/100

performance

10.5s

load time

600Kb

2.6M

javascript

87Kb

images

JS optimization

100Kb JS

100Kb CSS

>

100Kb JS

100Kb IMG

>

#6

Code splitting

render() {
 return (
  <div className="app">
    <Switch>
      <Route exact path="/" component={LandingPage} />
      <Route path="/trials" component= {TrialsPage} />
      <Route exact path="/home" component={HomePage} />
      <Route path="/team/:adminUsername/:teamId" component={TeamPage} />
      <Route exact path="/exercise/new" component={NewExercisePage} />
      <Route exact path="/join/:joinHash" component={JoinPage} />
      <Route path="/login" component={LoginPage} />
      <Route path="/privacy" component= {PrivacyPage} />
      <Route path="/press-kit" component= {PressKitPage} />
      <Route component={NotFound} />
    </Switch>
  </div>
 );
}
<Route exact path="/" component={LazyLanding} />
import React from 'react';

import Loadable from 'react-loadable';
import RouterLoading from '../RouterLoading.component';

const LazyLandingPage = Loadable({
    loader: () => import('./Landing.page'),
    modules: ['./Landing.page'],
    webpack: () => [require.resolveWeak('./Landing.page')],
    loading: RouterLoading
});

export default (props) => <LazyLandingPage {...props} />;
#7

Aggressive code splitting

=    15/70Kb

How about loading it just when I need it? 

AKA: on button click
onClick() {
  import('../Swal.service').then(SwalService => {
    /* Use it */
  });
}

Treeshaking

Already done

#8

Bundlephobia.com

#9

Check our progress #2

91/100

performance

2.5s

load time

74Kb

260Kb

javascript

87Kb

images

Next level ❤

Server Side Rendering

#10

SW Precaching

#10
self.addEventListener('fetch', function onFetch(event) {
    if (event.request.url.indexOf(location.origin) === 0) {
        event.respondWith(cacheOrNetwork(event));
    }
});

Download from a Service Worker all the other

chunks & pre cache them.

Skeleton Screens

#11

🔍 Monitor

Bundlesize 📦

{
  "bundlesize": [{
    "path": "./dist/**/*.css",
    "compression": "none",
    "maxSize": "50 kB"
   },
   {
     "path": "./dist/**/!(*.worker).js",
     "compression": "none",
     "maxSize": "300 kB"
   },
   {
     "path": "./dist/**/*_high.jpg",
     "maxSize": "800 kB"
   },
   {
     "path": "./dist/**/*_med.jpg",
     "maxSize": "200 kB"
   },
   {
     "path": "./dist/**/*_low.jpg",
     "maxSize": "100 kB"
   },
   {
     "path": "./dist/**/!(*_high|*_med|*_low|*_seo*).jpg",
     "compression": "none",
     "maxSize": "100 kB"
   }],
  "scripts": {
    "perf-test": "bundlesize"
  }
}

and and...

Lighthouse CLI 😍

$ lighthouse http://localhost:1234 
  --output json --output-path ./audit.json
but now what...?

Thank you!