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..889169eea 100644 --- a/middleware/static_test.go +++ b/middleware/static_test.go @@ -44,6 +44,67 @@ func TestStatic_useCaseForApiAndSPAs(t *testing.T) { } +func TestStaticHTML5PreservesMatchedHandlerNotFound(t *testing.T) { + var testCases = []struct { + name string + buildEcho func() *echo.Echo + whenURL string + expectCode int + expectJSONEq string + expectContains string + }{ + { + name: "ok, router-matched handler 404 is preserved instead of serving index", + buildEcho: func() *echo.Echo { + 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") + }) + return e + }, + whenURL: "/api/users/42", + expectCode: http.StatusNotFound, + expectJSONEq: `{"message":"user not found"}`, + }, + { + name: "ok, router-level 404 for group without matched route still serves index", + buildEcho: func() *echo.Echo { + e := echo.New() + e.Group("/app", StaticWithConfig(StaticConfig{ + Root: "testdata/dist/public", + HTML5: true, + })) + return e + }, + whenURL: "/app/dashboard", + expectCode: http.StatusOK, + expectContains: "