Skip to content

Build output in "versioned folder" (build/static/<versioned-hash-folder>/) instead of "build/static/" #9216

@kashifshamaz21

Description

@kashifshamaz21

Problem

When CRA app is served over a CDN, the Cache headers suggested by the docs are as follows:

index.html  Cache-Control: no-cache
js/css/img  Cache-Control: max-age: 31536000

Doing this way, the problem is that whenever browser fetches index.html, it would need to validate with the origin server (lets say s3) if its local copy is valid, and if its not expired (using ETag) browser can use local cache copy, or else it would download the html. Though this doesn't incur bandwidth (when cached copy is still valid), it would incur a roundtrip latency each time to load index.html. In some sense, defeats the purpose of serving the app over CDN, ideally the index.html should be served directly from the CDN without setting Cache-Control: no-cache on it.

Why Cache-Control: no-cache is needed?
Because the generated index.html is modified each time the static bundle files change. If the index.html is not configured with Cache-Control: no-cache, the browser may get a older/cached index.html from CDN, referencing older static bundles. When it would try to download those older static files from the CDN, and in case there is a cache miss, it would result in 404s as the origin server no longer has those older static files.

Proposed Solution

Generate the static output files in a versioned folder instead of build/static:

my-app
|__ build
    |__ static
         |___<versioned-folder-hash>
               |__ js
               |__css
               |__media

index.html:
<script src="/static/<versioned-folder-hash>/js/2.chunk.js"></script>
<script src="/static/<versioned-folder-hash>/js/main.chunk.js"></script>

asset-manifest.json:
{ 
   "files" : {
         "main.js": "/static/<versioned-folder-hash>/js/main.chunk.js",
         ...
   },
   "entrypoints": [
         "static/<versioned-folder-hash>/js/main.chunk.js"
   ],
   "version": "<versioned-folder-hash>"
}

Advantages of above build output structure:

  1. Better caching of index.html: Cache-Control: no-cache is no longer needed on index.html.
    a) Whenever a new build is deployed to your origin server, you can run a script to cache bust index.html. It would take a few mins for all edge servers to reflect the index.html, in that time window, even if some browsers get served with older index.html from CDN, the older static bundles referenced in that index.html would still be present in the Origin server (you don't delete the n-1 release bundle yet. The clean up of older build can be done after n+1 release, by then all edge servers would be serving release n).

    b) Until the next release is deployed, the index.html file would be served directly from CDNs, and no longer have the roundtrip latency of validating the freshness of the cached file from Origin server.

App deploy folder structure on Origin Server would potentially look like this:

<app-root>
  |___ index.html
  |___ asset-manifest.json
  |___ static
        |___<versioned-folder-hash-1> // release 'n'
             |__ js
             |__css
             |__media
        |___<versioned-folder-hash-2> // release 'n-1'

Current Alternative to achieve the above:

Though I don't want to go this route, I am probably going to have to eject the config from cra and make modifications in config files to achieve the above build output structure.

I would be glad if someone in the community can help me understand if the above problem is solvable in some other way, without having to eject the configs / my understanding of the Caching problem is inaccurate.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions