[Solved] Serving Pre-compressed Files (SPA)

Just wanted to put this out there, in case anyone else wants to do the same. I’m building an SPA with parcel and I wanted to set appropriate caching and pre-compressed file support.

My configuration is as follows:

:8080

root * /srv

log {
  format json
}

##################################
# *.html pre-compressed files
##################################
  @htmlBr {
    path *.html
    header Accept-Encoding *br*
    file {
      try_files {path}.br
    }
  }
  handle @htmlBr {
    rewrite {path}.br
    header Content-Encoding br
    header Content-Type text/html
    header Cache-Control max-age=300,public
    header Vary "Accept-Encoding"
  }
  @htmlGzip {
    path *.html
    header Accept-Encoding *gzip*
    file {
      try_files {path}.br
    }
  }
  handle @htmlGzip {
    rewrite {path}.gz
    header Content-Encoding gzip
    header Content-Type text/html
    header Cache-Control max-age=300,public
    header Vary "Accept-Encoding"
  }
  @html {
    path *.html
  }
  handle @html {
    header Cache-Control max-age=300,public
    header Vary "Accept-Encoding"
  }
##################################


##################################
# *.js pre-compressed files
##################################
  @jsBr {
    header Accept-Encoding *br*
    path *.js
    file {
      try_files {path}.br
    }
  }
  handle @jsBr {
    rewrite {path}.br
    header Content-Encoding br
    header Content-Type application/javascript
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @jsGzip {
    header Accept-Encoding *gzip*
    path *.js
    file {
      try_files {path}.br
    }
  }
  handle @jsGzip {
    rewrite {path}.gz
    header Content-Encoding gzip
    header Content-Type application/javascript
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @js {
    path *.js
  }
  handle @js {
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
##################################



##################################
# *.map pre-compressed files
##################################
  @mapBr {
    header Accept-Encoding *br*
    path *.map
    file {
      try_files {path}.br
    }
  }
  handle @mapBr {
    rewrite {path}.br
    header Content-Encoding br
    header Content-Type application/json
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @mapGzip {
    header Accept-Encoding *gzip*
    path *.map
    file {
      try_files {path}.br
    }
  }
  handle @mapGzip {
    rewrite {path}.gz
    header Content-Encoding gzip
    header Content-Type application/json
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @map {
    path *.map
  }
  handle @map {
    header Content-Type application/json
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
##################################


##################################
# *.css pre-compressed files
##################################
  @cssBr {
    header Accept-Encoding *br*
    path *.css
    file {
      try_files {path}.br
    }
  }
  handle @cssBr {
    rewrite {path}.br
    header Content-Encoding br
    header Content-Type "text/css; charset=utf-8"
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @cssGzip {
    header Accept-Encoding *gzip*
    path *.css
    file {
      try_files {path}.br
    }
  }
  handle @cssGzip {
    rewrite {path}.gz
    header Content-Encoding gzip
    header Content-Type "text/css; charset=utf-8"
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
  @css {
    path *.css
  }
  handle @css {
    header Cache-Control max-age=2628000,public
    header Vary "Accept-Encoding"
  }
##################################



##################################
# Cache Control
# Note: other handlers set values above
  # cache favicon for 1 day
  @cacheFavicon {
    path_regexp favicon\.
  }
  handle @cacheFavicon {
    header Cache-Control max-age=86400,public
  }

  # 5 minutes caching by default fallback
  header Cache-Control max-age=300,public
##################################


##################################
# Support for SPA.
try_files {path} /index.html

file_server

Edit: add Vary header and default handlers for pre-compressed types

1 Like

Thanks for sharing this!

Would you be willing to write a similar post for our wiki category? That is perfect for examples and explanations. Especially if you reduce the config down to the simplest / crux parts, I think a lot of people would find that helpful.

You could also edit this post and change its category to Wiki, I think. If not let me know. :slight_smile:

Just a tip to reduce lines a bit:

    file {
      try_files {path}.br
    }

:point_down:

    file {path}.br

And also:

  @cacheFavicon {
    path_regexp favicon\.
  }

:point_down:

  @cacheFavicon path_regexp favicon\.

This one’s a bit more fun — you might be able to make use of import args to parameterize all of this to reduce the amount of lines:

Maybe something like this:

(precompressed) {
  @{args.0}{args.1} {
    header Accept-Encoding *{args.2}*
    path *.{args.0}
    file {path}.{args.1}
  }
  handle @{args.0}{args.1} {
    rewrite {path}.{args.1}
    header Content-Encoding {args.2}
    header Content-Type {args.3}
    header Cache-Control max-age={args.4},public
  }
}

...

import precompressed html br br text/html 300
import precompressed html gz gzip text/html 300
import precompressed js br br application/javascript 2628000
import precompressed js gz gzip application/javascript 2628000
import precompressed map br br application/json 2628000
import precompressed map gz gzip application/json 2628000
import precompressed css br br "text/css; charset=utf-8" 2628000
import precompressed css gz gzip "text/css; charset=utf-8" 2628000

Give this a shot, I didn’t test to confirm this will do exactly the right thing. But I hope so!

You can use the caddy adapt command to get the JSON representation of the Caddyfile to make sure that it ends up looking about the same as your long version.

2 Likes

Thank you… relatively new to caddy… been using nginx forever and wanted to get away from non-default container and support a read-only deployment for static content.

Will try your suggestions later, will also followup with a blog article and add to the wiki/examples as suggested

Tried the following, but getting an error: Error during parsing: File to import not found: precompressed

caddy version → v2.2.1 h1:Q62GWHMtztnvyRU+KPOpw6fNfeCD3SkwH7SfT1Tgt2c=

(precompressed) {
  @{args.0}{args.1} {
    header Accept-Encoding *{args.2}*
    path *.{args.0}
    file {path}.{args.1}
  }
  handle @{args.0}{args.1} {
    rewrite {path}.{args.1}
    header Content-Encoding {args.2}
    header Content-Type {args.3}
    header Cache-Control max-age={args.4},public
    header Vary "Accept-Encoding"
  }
}
(uncompressed) {
  @{args.0}{args.1} {
    path *.{args.0}
    file {path}.{args.1}
  }
  handle @{args.0}{args.1} {
    header Cache-Control max-age={args.4},public
    header Vary "Accept-Encoding"
  }
}

import precompressed html br br text/html 300
import precompressed html gz gzip text/html 300
import uncompressed html - - - 300
import precompressed js br br application/javascript 2628000
import precompressed js gz gzip application/javascript 2628000
import uncompressed js - - - 2628000
import precompressed map br br application/json 2628000
import precompressed map gz gzip application/json 2628000
import uncompressed map - - - 2628000
import precompressed css br br "text/css; charset=utf-8" 2628000
import precompressed css gz gzip "text/css; charset=utf-8" 2628000
import uncompressed css - - - 2628000

Snippet definitions should go at the top, before your site address, but after global options.

Also I recommend changing {args.4} to {args.2} in your second one so you don’t need to do - to fill out unused ones. You could add a comment above the snippet to explain the purpose of each arg if you want to document it for your future self :sweat_smile:

Have it working via separate files, and I did change the params for uncompressed… Didn’t know it had to go before the :8080 line…

Will try updating later.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.

For whoever finds this in the future, this is now possible without the funky snippets since v2.4.0, using the file_server directive’s precompressed option.

1 Like