diff --git a/middleware/static.go b/middleware/static.go index 62cf04e34..200f96a30 100644 --- a/middleware/static.go +++ b/middleware/static.go @@ -269,10 +269,12 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) { } var he echo.HTTPStatusCoder - if !errors.As(err, &he) || !config.HTML5 || he.StatusCode() != http.StatusNotFound { + if (c.Path() != "" && c.RouteInfo().Method != echo.RouteNotFound) || + !errors.As(err, &he) || + !config.HTML5 || he.StatusCode() != http.StatusNotFound { return err } - // is case HTML5 mode is enabled + echo 404 we serve index to the client + // In HTML5 mode, serve index for a router-level 404. file, err = currentFS.Open(config.Index) if err != nil { return err diff --git a/middleware/static_test.go b/middleware/static_test.go index 3567c0a55..bf3f10081 100644 --- a/middleware/static_test.go +++ b/middleware/static_test.go @@ -44,6 +44,36 @@ func TestStatic_useCaseForApiAndSPAs(t *testing.T) { } +func TestStaticHTML5PreservesMatchedHandlerNotFound(t *testing.T) { + e := echo.New() + e.Use(StaticWithConfig(StaticConfig{ + Root: "testdata/dist/public", + HTML5: true, + })) + e.GET("/api/users/:id", func(c *echo.Context) error { + return echo.NewHTTPError(http.StatusNotFound, "user not found") + }) + + req := httptest.NewRequest(http.MethodGet, "/api/users/42", nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusNotFound, rec.Code) + assert.JSONEq(t, `{"message":"user not found"}`, rec.Body.String()) + + group := echo.New() + group.Group("/app", StaticWithConfig(StaticConfig{ + Root: "testdata/dist/public", + HTML5: true, + })) + req = httptest.NewRequest(http.MethodGet, "/app/dashboard", nil) + rec = httptest.NewRecorder() + group.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "