Skip to content

[Suggestion] Allow headers to be specified as part of navigationFallback configuration #698

Open
@mattwilson1024

Description

@mattwilson1024

Background

My app is an Angular SPA. As part of a production build, Angular's CLI gives me "versioned" JS/CSS files containing a hash (e.g. main.[hash].js) and these files are referenced inside index.html.

If the user's browser (or any intermediary CDN) uses a cached index.html that is out of date, it may refer to old JS files (e.g. main.[oldhash].js) which no longer exist, resulting in them not seeing the latest version of the app. As such, caching needs to be configured to ensure that a cached index.html can only be used if it is first validated as being up to date (e.g. by validating the etag)

Is your feature request related to a problem? Please describe.

There does not seem to be an easy way to configure what headers should be attached to responses that are served due to the navigationFallback. This makes it hard to get fine grained control of caching headers for an SPA which requires client-side routing.

For my test I set up a Static Web App in combination with Azure Front Door. To give an example, take the following staticwebapp.config.json:

{
  "routes": [
    {
      "route": "/index.html",
      "headers": {
        "Cache-Control": "no-cache",
      }
    },
    {
      "route": "/",
      "headers": {
        "Cache-Control": "no-cache",
      }
    }
  ],
  "navigationFallback": {
    "rewrite": "index.html",
    "exclude": ["/assets/*"]
  },
  "globalHeaders": {
    "Cache-Control": "public, max-age=3600",
  },
  ...
}

With this configuration:

a) requesting mysite.com/index.html serves index.html with no-cache response header (due to the first route-specific config)

b) requesting mysite.com/ serves index.html with no-cache response header (due to the second route-specific config)

c) requesting mysite.com/somethingelse serves index.html with public, max-age=3600 response header (navigationFallback applies, but it uses the headers from globalHeaders and not the index.html route config)

By the nature of it being a fallback, there is no obvious pattern that could be used in the routes section of the config to capture the "fallback" paths as this could be any string except one where a file exists on disk with that name.

Describe the solution you'd like

I'd like all three of the above situations to serve the no-cache header to ensure that the user always receives the latest index.html (because it would be forced to validate the etag). Any requests to specific files that do exist (such as somescript.[hash].js) should use the globalHeaders (webpack bundled JS files with a hash in the name are suitable for more aggressive caching).

As far as I can gather, routes are not applied on NavigationFallback by design, but this means it is hard to apply specific configuration to them in "scenario C" above.

My suggestion would be to add the ability to specify headers as part of the navigationFallback section.

  "navigationFallback": {
    "rewrite": "index.html",
    "exclude": ["/assets/*"],
    "headers": {        <-- note: this is my suggestion, not something that is currently supported
        "Cache-Control": "no-cache",
      }
  },

Describe alternatives you've considered

My current workaround is to negate the logic (conservative caching by default, more aggressive caching for specific routes with a wildcard pattern). This seems to work, but is fiddlier to set up & understand and has the potential to result in new files being cached too heavily if happen to match the pattern.

My workaround works as follows:

  • sets no-cache as the global default header, such that all requests need to validate the etag before using a cached version
  • for any files inside the assets directory (containing assets which may be subject to change over time), set no-cache header. Because routes are matched in order and it stops at the first match, they will not be affected by any subsequent route parameters in the array.
  • for specific file patterns outside of assets such as JS/CSS files (which the Angular/Webpack build process should have added hashes for, making them suitable for more aggressive caching), set public, max-age=3600 header so that they can potentially avoid network requests entirely
{
  "routes": [
    {
      "route": "/assets/*",
      "headers": {
        "Cache-Control": "no-cache"
      }
    },
    {
      "route": "/*.{js,css,ttf,woff,eot,svg}",
      "headers": {
        "Cache-Control": "public, max-age=3600"
      }
    }
  ],
  "navigationFallback": {
    "rewrite": "index.html",
    "exclude": ["/assets/*"]
  },
  "globalHeaders": {
    "Cache-Control": "no-cache"
  },
  ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions