diff --git a/site/nextrouter/nextrouter.go b/site/nextrouter/nextrouter.go index adf9a00cd23d4..caa84c4065e42 100644 --- a/site/nextrouter/nextrouter.go +++ b/site/nextrouter/nextrouter.go @@ -49,10 +49,7 @@ func Handler(fileSystem fs.FS, options *Options) (http.Handler, error) { } // Fallback to static file server for non-HTML files - // Non-HTML files don't have special routing rules, so we can just leverage - // the built-in http.FileServer for it. - fileHandler := http.FileServer(http.FS(fileSystem)) - router.NotFound(fileHandler.ServeHTTP) + router.NotFound(FileHandler(fileSystem)) // Finally, if there is a 404.html available, serve that err = register404(fileSystem, router, *options) @@ -64,6 +61,31 @@ func Handler(fileSystem fs.FS, options *Options) (http.Handler, error) { return router, nil } +// FileHandler serves static content, additionally adding immutable +// cache-control headers for Next.js content +func FileHandler(fileSystem fs.FS) func(writer http.ResponseWriter, request *http.Request) { + // Non-HTML files don't have special routing rules, so we can just leverage + // the built-in http.FileServer for it. + fileHandler := http.FileServer(http.FS(fileSystem)) + + return func(writer http.ResponseWriter, request *http.Request) { + // From the Next.js documentation: + // + // "Caching improves response times and reduces the number + // of requests to external services. Next.js automatically + // adds caching headers to immutable assets served from + // /_next/static including JavaScript, CSS, static images, + // and other media." + // + // See: https://nextjs.org/docs/going-to-production + if strings.HasPrefix(request.URL.Path, "/_next/static/") { + writer.Header().Add("Cache-Control", "public, max-age=31536000, immutable") + } + + fileHandler.ServeHTTP(writer, request) + } +} + // registerRoutes recursively traverses the file-system, building routes // as appropriate for respecting NextJS dynamic rules. func registerRoutes(rtr chi.Router, fileSystem fs.FS, options Options) error { diff --git a/site/nextrouter/nextrouter_test.go b/site/nextrouter/nextrouter_test.go index 8e0a807fe1bd2..2f7c9c14d9a65 100644 --- a/site/nextrouter/nextrouter_test.go +++ b/site/nextrouter/nextrouter_test.go @@ -376,6 +376,42 @@ func TestNextRouter(t *testing.T) { require.EqualValues(t, "test-create", body) }) + t.Run("Caching headers for _next resources", func(t *testing.T) { + t.Parallel() + + rootFS := fstest.MapFS{ + "index.html": &fstest.MapFile{ + Data: []byte("test-root"), + }, + "_next/static/test.js": &fstest.MapFile{ + Data: []byte("test.js cached forever"), + }, + "_next/static/chunks/app/test.css": &fstest.MapFile{ + Data: []byte("test.css cached forever"), + }, + } + + router, err := nextrouter.Handler(rootFS, nil) + require.NoError(t, err) + + server := httptest.NewServer(router) + t.Cleanup(server.Close) + + res, err := request(server, "/index.html") + require.NoError(t, err) + require.NoError(t, res.Body.Close()) + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Empty(t, res.Header.Get("Cache-Control")) + + res, err = request(server, "/_next/static/test.js") + require.NoError(t, err) + require.NoError(t, res.Body.Close()) + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, "public, max-age=31536000, immutable", res.Header.Get("Cache-Control")) + }) + t.Run("Injects template parameters", func(t *testing.T) { t.Parallel()
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: