Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content-addressable naming for static resources #1473

Closed
ide opened this issue Mar 21, 2017 · 7 comments
Closed

Content-addressable naming for static resources #1473

ide opened this issue Mar 21, 2017 · 7 comments

Comments

@ide
Copy link

ide commented Mar 21, 2017

Currently when you write <img src="/static/icon.png" /> that's what Next emits in HTML. If icon.png changes, the browser needs to know to redownload it so Next can't use cache-control: immutable and has to use ETags and 304s.

If Next's build steps converted the icon's URL to the hash of the icon file (this generalizes to any static resource) and emitted <img src="/static/595f44fec1e92a71d3e9e77456ba80d1.png" /> then Next could serve the resource with cache-control: max-age=31536000, immutable. This is awesome for performance and is similar to what Next does with its JSON chunks (talking to @arunoda).

Implementation idea:

The trickiest part here is for Next to be able to rewrite the image URL for both the server- and client-side JS. One approach could be to introduce a function that rewrites URLs:

// App code:
<img src={next.static('/static/icon.png')} />

-----------

// The Next.js build step auto-generates a big mapping:
const __staticResourceMap = {
  '/static/icon.png': '/static/595f44fec1e92a71d3e9e77456ba80d1.png',
};

next.static = (url) => __staticResourceMap[url];

This becomes even more useful when next.static() prepends a CDN URL:

// Assume that next.config.getStaticResourceUri() is custom and defined by the developer.
// A common use case would be to prepend the CDN base URL.
next.static = (url) => next.config.getStaticResourceUri(__staticResourceMap[url]);

Optimization:

Since the static resource map could become very large, we can completely avoid sending it to the client by inlining it using a simple Babel transform. This Babel transform would convert next.static('/static/icon.png') to '/static/595f44fec1e92a71d3e9e77456ba80d1.png' all at compile-time. Therefore, if the Babel transform is successfully able to inline all next.static() calls, then there's no need to send the implementation of next.static() and the static-resource map to the browser!

@arunoda
Copy link
Contributor

arunoda commented Mar 22, 2017

Actually, in this case we don't need a mapping. And keeping such a mapping and moving it to the client is kind a overkill.

So, doing this is pretty simple. And we don't need to go for hashes as well. (Hashes are better but comes with a computational problem. Since we can't predict the size of the files, my suggestion is to get away from the hashes)

So, what next.static does is pretty simple. It'll prepend the buildId to the URL. So, the URL will be like this:

/:buildId/image/path

I suggest an API like this:

import static from 'next/static'
const url = static(`my-image.png`)

@ide
Copy link
Author

ide commented Mar 22, 2017

One of the benefits of the hashes is that the URLs don't change across deploys. I find that on most websites, I make frequent changes to the code but my images don't change a lot, and I don't want to feel that I can't deploy as often because it will bust browser caches (ETags and 304s would perform better).

Looking at benchmarks of tools like md5sum, it looks like the bottleneck is mostly the disk I/O of reading the files and it can process 100-200MB/sec.

@arunoda
Copy link
Contributor

arunoda commented Mar 22, 2017

@ide that's true. I wanted to start shipping something soon.
We can't do big changes now since we are pretty close to 2.0.

As a proper solution, I'd like to use a babel-transformer which build the hash and add the URL statically. In that case, we may need to copy files as well. That's a big change and that's I wanted to wait a bit.

May be if we went for a proper webpack solution in 3.0, we don't need to re-implement this ourselves.

@ide
Copy link
Author

ide commented Mar 22, 2017

As an end-user of Next, I'd be happy with seeing this feature be opt-in in a minor version like v2.1. I like the Babel plugin idea much more than using Webpack, I find it much simpler to reason about the individual steps (next build first copies and renames files, then it gives the original filename => hash map to the Babel plugin).

@cloudkite
Copy link
Contributor

@arunoda are you interested in PR's for this? I have a internal babel plugin that could be generalised + tidied up. currently the output path is hardcoded, as I dont really know what the best approach is for next telling the babel-plugin the location it wants the hashed files outputted, any ideas on this?

@arunoda
Copy link
Contributor

arunoda commented May 31, 2017

@cloudkite I'm not exactly sure how to answer. But try to store in _next. We can discuss this in the PR.
I'd love to have it.

@cloudkite cloudkite mentioned this issue Jun 13, 2017
2 tasks
@timneutkens
Copy link
Member

https://github.com/zeit/next.js/tree/canary/examples/with-hashed-statics

@lock lock bot locked as resolved and limited conversation to collaborators May 12, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants