惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Security Latest
Security Latest
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
D
Docker
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
Schneier on Security
Schneier on Security
小众软件
小众软件
爱范儿
爱范儿
GbyAI
GbyAI
J
Java Code Geeks
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Securelist
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Y
Y Combinator Blog
S
Schneier on Security
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
F
Fortinet All Blogs
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
V
V2EX
V2EX - 技术
V2EX - 技术
O
OpenAI News
W
WeLiveSecurity

Bryan Robinson's Blog

Does our technology still work for us? Product pricing, dev bad habits, and the role of the pit of success Astro Server Island for latest Bluesky post (heavily cached!) Type-safe environment variables in Astro 5.0 New Website, but really is it? Netlify Durable Cache: Caching for a third-party world Introducing the Hygraph Astro Content Loader Integrating Astro.js Starlight Documentation into a Next.js Project Using Proxies Jamstack is meaningless 😱 Book Release: Eleventy by Example – Learn 11ty with 5 in-depth projects 11ty Second 11ty: Creating Template Filters 11ty Second 11ty: Global Data files (JS and JSON) 11ty second 11ty: The Render Plugin Part 1 Help needed: Netlify Frontend environment variables with Astro.js Quick experiment with the Slinkity 11ty plugin Creating a dynamic color converter with 11ty Serverless Using 11ty JavaScript Data files to mix Markdown and CMS content into one collection How to show your template code in 11ty blog posts New City, New Job, New Content Using Nunjucks Three JAMstack movements to watch in 2020 Create a Codepen promo watermark with no additional HTML, CSS or JS 3 underused CSS features to learn for 2020 Use CSS Subgrid to layout full-width content stripes in an article template Adapt client-side JavaScript for use in 11ty (Eleventy) data files CSS Gap creates a bright future for margins in Flex as well as Grid Create your first CSS Custom Properties (Variables) Use CSS Grid to create a self-centering full-width element Creating an 11ty Plugin - SVG Embed Tool Now offering design and code reviews at PeerReviews.dev Routing contact-form emails to different addresses with Netlify, Zapier and SendGrid Create an Eleventy (11ty) theme based on a free HTML template Client work and the JAMstack Grid vs. Flex: A Tale of a "Simple" Promo Space Using Eleventy The Tech Barrier to Entry What Can We Learn from CERN Let Practical CSS Grid - Launching My First Course Build Trust on the Web incorporating User Worries with your User Stories 2019 The Year of Markup-First Development Refactoring CSS into a Sass mixin Starting a new journey with Code Contemporary Dynamic Static Sites with Netlify and iOS Shortcuts Top 3 uses for the ::before and ::after CSS pseudo elements How To: Use CSS Grid to Mix and Match Design Patterns Use CSS ::before and ::after for simple, spicy image overlays Modern CSS: Four Things Every Developer and Designer Should Know About CSS 3 Strategies for Getting Started with CSS Grid CSS Tip: Use rotate() and skew() together to introduce some clean punk rock to your CSS The 5 Stages of Grid Love How To: A CSS-Only Mobile Off Canvas Navigation How To: Use CSS Grid Layout to Make a Simple, Fluid Card Grid Make a More Flexible Cover Screen with CSS Grid Can CSS Grid open up interesting CMS Layout options? Firefox 52 to Introduce New Box-Alignment Values Falling Forward — Rethinking Progressive Enhancement, Graceful Degradation and Developer Morality Start Exploring the Magic of CSS Grid Layout I Converted My Blog to CSS Grid Layout and Regret Nothing Feature Queries are on the Rise CSS Shapes — Let the Text Flow Around You Flexbox -- Let Memorializing Prince and Print vs. The Web I went to Italy and noticed UX fails How to Get Designers to Contribute in Open Source The True Gift of Your Former Code
Climbing the 11ty Performance leaderboard with Cloudinary, critical CSS and more
2020-03-25 · via Bryan Robinson's Blog

A few weeks ago, Zach Leatherman announced that he was going to change the Built with 11ty section 11ty’s documentation. He was going to create a performance leaderboard to showcase performant projects using the tool.

My site wasn’t on there, so I decided to add it in for round 2. I knew I wouldn’t be very high. Most of the top 100 sites from the first run had a Lighhouse score of 100 (a pretty great testament to the 11ty community).

Only the top 200 have a ranking. My site was ranked 187 with a Lighthouse score of 91. Overall, I was happy to be ranked, but accepted the challenge of breaking into the top 100.

A few criteria for increasing my performance rank

While I could have removed all images from my homepage and did a whole bunch of task to superficially bump my score, I wanted to do this to help my site as a whole. 

I left my design unchanged. The homepage still has the same number of images as before, the CSS is still creating the same layouts and styles.

This was all to be invisible changes to increase the performance of my site as it stood. No gaming the system. Just good honest performance.

The path from Lighthouse 95 to Lighthouse 100

{% respimg “https://bryanlrobinson.com/images/performance-audit/current-audit.jpg” “Current Lighthouse score Criteria as detailed below” “(max-width: 640px) 640px, 900px” %}

I started by running a Lighthouse report on my current site. When run from Chrome’s Audit panel, I registered an 80. When run from the Lighthouse CLI tool — which is what Zach uses — I got a 95.

Here are the score breakdowns from the CLI:

  • First Contentful Paint: 1.7s
  • Speed Index: 2.6s
  • Time to Interactive: 3.5s
  • First Meaningful Pain: 1.7s
  • First CPU Idle: 3.5s
  • Max Potential First Input Delay: 2,440ms

The recommendations were the same either way: 

  • Serve Images in next-gen formats
  • Efficiently encode images
  • Eliminate render-blocking resources
  • Preconnect to required origins

Let’s take those one at a time.

Dealing with images: Implementing Cloudinary on 11ty

The biggest estimated wins were around images. With those two items, Lighthouse estimated I could save 2.87s off my page load. Nothing to sneeze at!

I could have gone through and optimized the images myself and converted them to “next-gen” formats manually, but that sounded like an on-going pain.

I chose to implement Cloudinary images instead. Cloudinary is a CDN-based media host. You store (or copy) your images into your own personal bucket and then you can enjoy loads of transformations on the images.

This allows a developer to efficiently create responsive images with proper srcset sizes. It also allows for on-the-fly optimizations with just a few query parameters. This gave me those “next-gen” file types served dynamically to browsers that support them. On top of that, it also dynamically sends images that have their quality optimized for speed. Super handy in this situation.

To do this, I used an 11ty plugin: Respimg.

This package automatically connects to Cloudinary and creates base srcsets based on some small configuration. I had it up and running in about 20 minutes. My biggest hurdle was making sure to  allow my Cloudinary account to accept “fetch” requests from my domain. The plugin let me keep my main images folder directly in my site’s repository. This let me implement the integration faster.

Code Input in templates with the plugin

{% respimg
  "https://bryanlrobinson.com/images/headshot.jpg",
  "Bryan's smiling face, glasses slightly askew",
  "(min-width: 48em) 200px, 600px",
  "homepage-headshot"
%}

Code Output from the above template tag

<img 
  src="https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_640/https://bryanlrobinson.com/images/headshot.jpg" 
  srcset="https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_320/https://bryanlrobinson.com/images/headshot.jpg 320w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_640/https://bryanlrobinson.com/images/headshot.jpg 640w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_960/https://bryanlrobinson.com/images/headshot.jpg 960w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_1280/https://bryanlrobinson.com/images/headshot.jpg 1280w" 
  sizes="(min-width: 48em) 200px, 600px" 
  alt="Bryan's smiling face, glasses slightly askew">

I did move away from using the plugin and created my own local version of it. There were two features I felt were missing: ability to add a class to the image and the ability to use loading=lazy on the image. With my local version, I implemented both. I may go back and submit them to the package at a later time.

New Template Syntax

{% respimg
    "https://bryanlrobinson.com/images/headshot.jpg",
    "Bryan's smiling face, glasses slightly askew",
    "(min-width: 48em) 200px, 600px",
    "homepage-headshot"
%}

New Output

<img 
  loading="lazy" 
  src="https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_640/https://bryanlrobinson.com/images/headshot.jpg" 
  srcset="https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_320/https://bryanlrobinson.com/images/headshot.jpg 320w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_640/https://bryanlrobinson.com/images/headshot.jpg 640w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_960/https://bryanlrobinson.com/images/headshot.jpg 960w, https://res.cloudinary.com/brob/image/fetch/q_auto,f_auto,w_1280/https://bryanlrobinson.com/images/headshot.jpg 1280w" 
  class="homepage-headshot" 
  sizes="(min-width: 48em) 200px, 600px" 
  alt="Bryan's smiling face, glasses slightly askew">

With this one change, I took my speed index from 5.0s to 2.2s and jumped my Lighthouse score to 97.

Inlining my CSS to reduce “render blocking resources”

The main issues with “Render Blocking Resources” are my CSS files and my Google Fonts. To fix this, we want to inline at least the critical CSS, but I decided to take it all the way to embed all our CSS on pages.

I started out by attempting to break my CSS up into components and use Nunjucks {% raw %}{% include %}{% endraw %} tags to bring them onto the page.

This worked, but I went down quite a few rabbit holes on how I wanted to structure this. Nothing felt quite right. I felt like I was fighting against my templates instead of them working for me. I went looking for a new solution.

I’ve followed Andy Bell for a while, and I love the way he thinks about things. I knew he’d done some work around inlining styles in 11ty, so I went perusing his GitHub repos.

On one of his latest “fun” sites — the awesome “Thanks for the Reply, Guy” site — had just the thing I was looking for.

He had created a “helper” function to handle concatenation and minification of CSS from a base set and a layout-specific set. This was perfect and I won’t lie, I ripped it right off! The great thing about it is that it gets to just be a JS data file in 11ty’s structure. Because 11ty has no issues with JS, you can run these functions as variable calls from your templates. 

It’s really kind of genius, to be honest. I’d never thought of data files like that.

const fs = require('fs');
const uglifycss = require('uglifycss');
const path = require('path');
const cssDir = path.join(__dirname, '..', '_includes', 'css');

module.exports = {
  getCSS(coreStyles, layoutStyles) {
    let css = '';
    let partials = [];
    if (coreStyles) {
      partials = [...partials, ...coreStyles];
    }
    if (layoutStyles) {
      partials = [...partials, ...layoutStyles];
    }
    if (partials.length) {
      partials.forEach(partial => {
          const cssPath = path.join(cssDir, partial);
        css += fs.readFileSync(cssPath);
      });
    }
    return uglifycss.processString(css);
  }
};

In my base.njk template, I use the helper to create an embedded style block with <style></style>, and use Nunjuck’s set blocks to create an array to our various CSS files.

I made a few changes as I went to handle things the way I needed. Mostly, I stripped out some fancy functionality for handling resets. I also changed the way the CSS files were found. Andy was working by copying the CSS files from his _includes to his _data directory using a tmp directory. This felt a little extra for me, so I used the Node path functionality to find where my CSS files lived.

Then, it was just a matter of breaking up my CSS into components. When I redesigned my site, I made the choice not to use a preprocessor, so I had to do that thinking now.

{% set coreCSS = [
    'base.css',
    'banner.css'
  ] 
%} 

<!-- On different layout files, we also set layoutCSS as a variable -->
{% set layoutStyles = [ 
    'articles.css', 
    'components/article-promo.css', 
    'components/ad-space.css', 
    'components/related-list.css' 
  ]
%}

<style>
  {{ helper.getCSS(coreCSS, layoutStyles) | safe }}
</style>

With my CSS inline, I now ran the risk of my initial HTML file being a big load. From there, I used a “transform” in 11ty to minify the HTML of each page. I, honestly, hate minified HTML, but I DO see how it can be super helpful to minimize file size of the initial download.

With these changes, I took my Lighthouse score to 99. So close! After perusing the leaderboard, I realized I would need a score of 100 to guarantee a placement in the top 100 sites.

Preconnect external resources and handle Google fonts asynchronously

As it turns out, the browser can be told to expect external connections. By telling the browser what domains it should expect to connect to, it can do some work behind the scenes to get ready for it even before a request is made.

To do this, you can create a new <link> tag in your header for each domain.

<link rel="preconnect" href="https://domain.com">

I did this for Google Analytics (which I should just remove, but I do like measuring my imaginary friends), Google fonts and Twitch (for an API call I make).

These last changes bumped me up to a score of 100!

We’ll see this weekend — and I’ll report back — if I break the top 100. If the sites remain as is, I should see that happen.

Final Results! Performance Score of 100

{% respimg “https://bryanlrobinson.com/images/performance-audit/after-changes.jpg” “Final results: Performance score up to 100. Details follow” “(max-width: 640px) 640px, 900px” %}

  • Performance Score: 100 (5 points up)
  • First Contentful Paint: 1.1s (.6s decrease)
  • Speed Index: 1.7s (.9s decrease)
  • Time to Interactive 2.4s (1.1s decrease)
  • First Meaningful Paint 1.1s (.6s decrease)
  • First CPU Idle: 1.2s (2.3s decrease)
  • Max Potential First Input Delay: 120ms (2,320ms decrease)

Why is this important?

With so many cool things happening the JAMstack recently, I think we should always keep an eye on performance. Many frameworks can sacrifice speed for developer convenience. The ideas behind hydration and rehydration can sometimes negatively impact this as well. First render is quick, but first useful render can be tediously slow.

I love that 11ty has decided to prominently call out sites that are doing well in this regard. It shows their goal of performant, HTML-driven sites. 

What can you do to improve your sites. Take a couple hours, see what the Lighthouse audit shows you and make some changes. Let me know on Twitter how it went!