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!

Optimize like your life depends on it!

By Pava

Optimize like your life depends on it!

Building a performant, non-trivial web app is a challenge every developer must take, be it just for educational purposes. It's no small feat to accomplish this, as it requires curiosity and perseverance, as well as the technical skills to back them up!

  • 357
Loading comments...

More from Pava