fix(server): return 404 for missing content-hashed assets
A /assets/ chunk from a previous build no longer exists after a deploy; serving the SPA shell at that URL makes the browser fail dynamic imports on a text/html module. Exclude /assets/ from the SPA fallback so the miss surfaces as a 404 the client can react to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
845d969af1
commit
bcc78e7332
@@ -156,6 +156,15 @@ func (h *frontendHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// A missing /assets/ file is a content-hashed chunk from another build,
|
||||
// not an app route: answering with the shell makes dynamic imports fail
|
||||
// on a text/html module. A 404 surfaces the real condition so the client
|
||||
// preload-error handler can reload onto the current build.
|
||||
if strings.HasPrefix(path, "/assets/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// SPA fallback: serve the (branded) index.html shell.
|
||||
shell, ok := h.brandedShell(r)
|
||||
if !ok {
|
||||
|
||||
@@ -79,6 +79,31 @@ func TestFrontendHandlerServesStaticAssetsWithoutCSP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrontendHandlerReturns404ForMissingAssets(t *testing.T) {
|
||||
handler := newFrontendTestHandler(t)
|
||||
|
||||
// A content-hashed chunk from a previous build no longer exists after a
|
||||
// deploy. Serving the SPA shell at a .js URL makes the browser fail with
|
||||
// "Failed to fetch dynamically imported module"; a 404 lets clients (and
|
||||
// the preload-error reload handler) see the real condition.
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/assets/view-OldHash.js", nil))
|
||||
|
||||
if rr.Code != http.StatusNotFound {
|
||||
t.Fatalf("missing asset status = %d, want 404", rr.Code)
|
||||
}
|
||||
if ct := rr.Header().Get("Content-Type"); strings.Contains(ct, "text/html") {
|
||||
t.Fatalf("missing asset served as HTML (%q), the SPA fallback must not swallow /assets/", ct)
|
||||
}
|
||||
|
||||
// Non-asset app routes still get the shell.
|
||||
rr = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/library/ebooks", nil))
|
||||
if rr.Code != http.StatusOK || !strings.Contains(rr.Header().Get("Content-Type"), "text/html") {
|
||||
t.Fatalf("SPA route = %d %q, want 200 HTML", rr.Code, rr.Header().Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
||||
func newFrontendTestHandler(t *testing.T) http.Handler {
|
||||
t.Helper()
|
||||
prev := WebDistFS
|
||||
|
||||
Reference in New Issue
Block a user