diff --git a/src/guide/runtime/routing.md b/src/guide/runtime/routing.md index a5f5ac53..ec568572 100644 --- a/src/guide/runtime/routing.md +++ b/src/guide/runtime/routing.md @@ -301,6 +301,62 @@ return [ ]; ``` +### Trailing slash canonical URLs + +Choose one URL shape for each resource. If `/docs` and `/docs/` render the same page, search engines and caches may +treat them as different URLs. Redirect one shape to the other before routing. + +The following middleware removes trailing slashes, preserves the query string, drops the fragment, and uses `308 +Permanent Redirect` so the HTTP method is preserved: + +```php +getUri(); + $path = $uri->getPath(); + + if ($path !== '/' && str_ends_with($path, '/')) { + $canonicalUri = $uri + ->withPath(rtrim($path, '/')) + ->withFragment(''); + + return $this->responseFactory + ->createResponse(Status::PERMANENT_REDIRECT) + ->withHeader(Header::LOCATION, (string) $canonicalUri); + } + + return $handler->handle($request); + } +} +``` + +Register this middleware before `Yiisoft\Router\Middleware\Router` in the application middleware stack. If only a +specific route group needs this behavior, attach it to that group instead. + +Use the opposite rule if your project treats trailing slashes as canonical. The important part is to generate links in +one form and redirect alternative forms consistently. + ## Generating URLs To generate URL based on a route, a route should have a name: