Show me the Cache!

Jan.2022

@AtilaFassina

They were talking about cash

We are talking about cache

@AtilaFassina

is it?

This is not a deck about money.

...or...

@AtilaFassina

@AtilaFassina

Lead Web Developer

“table” of contents

@AtilaFassina

WHAT

WHERE

HOW

you know what

@AtilaFassina

http://website.com?i-hate-cache

but no,

cache isn’t the villain

reuse of previously fetched resources
if nothing changed

@AtilaFassina

@AtilaFassina

Edge Functions

Serverless Functions

Your browser

...

@AtilaFassina

freshness

@AtilaFassina

When cache expires?

@AtilaFassina

Heuristic Freshness

expiration time
responseTime + freshness - currentAge

when client received the Response

😬

seconds since generated

@AtilaFassina

Calculate Freshness

Last-Modified
Date

last time resource changed

time response was originated

(date - lastModified) \div 10

@AtilaFassina

revving

it’s actually about revision, but that’s less cool

@AtilaFassina

Revving

for resources which change without a pattern of frequency

  • adds revision hash to file name
     
  • good for updating 2 interdependent cached resources
     
  • forcibly fetching a new resource

@AtilaFassina

validation

@AtilaFassina

validation headers

Last-Modified
Etag

Entity tag representing content 

Vary

list of headers which influence response

@AtilaFassina

Entity Tag

ETag: W/"<etag_value>"
ETag: "<etag_value>"

Weak validator

  • revision number
  • timestamp
  • content hash

identifies specific version of response’s content

EXAMPLES

@AtilaFassina

Vary

normalise your response
to avoid splitting caches too much

⚠️

identifies headers which affect content in response

Vary: *

factors other than the request headers influence response

Vary: Accept-Encoding

@AtilaFassina

Normalisation

Accept-Encoding: gzip,deflate
Accept-Encoding: gzip

😓

because browsers can’t agree... 🥲

@AtilaFassina

Normalisation

if (acceptEncoding.includes('br')) {
  res.setHeader('Accept-Encoding', 'br')
  
} else if (acceptEncoding.includes('gzip')) {
  res.setHeader('Accept-Encoding', 'gzip')
  
} else {
  res.removeHeader('Accept-Encoding') 
}

@AtilaFassina

control

@AtilaFassina

Cache-Control

one header to control it all

  • max-age
  • no-cache x no-store
  • private x public
  • stale-while-revalidate
  • must-revalidate
  • immutable

@AtilaFassina

immutable

don’t update while fresh

Cache-Control: public, max-age=604800, immutable

powerful combo when combined with revving/cache busting for static resources

💡

common use

@AtilaFassina

must-revalidate

Cache-Control: public, max-age=604800, must-revalidate

if can’t connect to origin server
to revalidate, can’t use resource

⚠️

common use

once stale, must validate before reuse

@AtilaFassina

stale-while-revalidate

Cache-Control: max-age=604800, stale-while-revalidate=86400

eliminates latency effect of
revalidation to the end-user

common use

use stale while revalidating

@AtilaFassina

using

@AtilaFassina

import type { NextFetchEvent, NextRequest } from 'next/server'

export function middleware(req: NextRequest, ev: NextFetchEvent) {  
  return new Response({
    headers: {
      'Cache-Control': 'private, max-age=43200, must-revalidate',
    }
  }) 
}

intercepts all requests going to sibling and child routes

💡

@AtilaFassina

import type { GetStaticProps } from 'next'

export const getStaticProps: GetStaticProps = () => {  
  const props = getMyProps()
  
  return {
    props,
    revalidate: 10
  }
}

re-renders the page, consequently refetching the resource

💡

@AtilaFassina

import type { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (context) => {
  context.res.setHeader('Cache-Control', 'max-age=600')

  return {
    props: {},
  }
}

this method behaves differently on client-side navigation and on server-side navigation

⚠️

@AtilaFassina

import type { LoaderFunction } from 'remix'

export function loader:LoaderFunction = () => {
  const responseBody = someLogic()
  
  return new Response(JSON.stringify(responseBody), {
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 'private, max-age=43200, must-revalidate',
    }
  }) 
}

 `loader` headers are automatically passed     down to page document request too

💡

@AtilaFassina

import type { HeadersFunction } from 'remix'

export function headers:HeadersFunction = () => {
  
  return {
      'Cache-Control': 'public, max-age=10, stale-while-revalidate=86400',
    }
}

so we can “fake” Incremental Static Regeneration

🤯

similar to `revalidate: 10` in Next.js

@AtilaFassina

goodie bag

@AtilaFassina

Cache-Control Goodies

Come back tomorrow

'cache-control': 'private, max-age=86400, immutable'

Always check no matter what!

'cache-control': 'no-cache'

Always update, but always serve from cache

'cache-control': 'public, max-age=1, stale-while-revalidate'

@AtilaFassina

Cache-Control Goodies

Yours for a day, maybe 2

'cache-control': 'private, max-age=86400, stale-while-revalidate=129600'

Ours for a day, maybe 2

'cache-control': 'public, max-age=86400, stale-while-revalidate=129600'

Tell me yours!

👉

@AtilaFassina

thank you

show me the cache

By Atila

show me the cache

"Oh, you found a bug? It's probably cache. " is the unfairest sentence on the web. Cache
is not the bug, not understanding cache is. Let's have a look on how it works and what we can 
do to leverage User Experience to the next level with it. Let's flush cache from a bug to a feature together!

  • 359