Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Trailing Slash Laravel Package

fsasvari/laravel-trailing-slash

A small Laravel package for consistent trailing slashes in URLs. It provides middleware and helpers to add, remove, or enforce trailing slashes, helping avoid duplicate routes and SEO issues while keeping redirects and canonical URLs predictable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and let Laravel's package discovery register the service provider:

composer require fsasvari/laravel-trailing-slash

Publish the config if you need to change the default policy (the exact tag is in the README; it is typically trailing-slash-config or config):

php artisan vendor:publish --provider="Fsasvari\LaravelTrailingSlash\TrailingSlashServiceProvider"

The package is intentionally small: it ships one middleware whose job is to compare the incoming path against your canonical trailing-slash policy and issue a redirect when they differ. Look first at:

  1. The published config file — this is where the add/remove behavior and any exclusions live.
  2. The middleware's handle() method — confirm whether it redirects GET only or all verbs, and whether it preserves the query string.
  3. README.md for the exact middleware FQCN and registration snippet, since that is the only integration point.

Register the middleware in the web group (or globally if you want it everywhere):

// bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php (Laravel 10 and below)
->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \Fsasvari\LaravelTrailingSlash\Middleware\TrailingSlash::class,
    ]);
})

Your first use case is almost always SEO/canonicalization: pick one form (/about/ or /about) and make every other form 301 to it so crawlers and analytics see a single URL. Verify with:

curl -I http://localhost:8000/about
# HTTP/1.1 301 Moved Permanently
# Location: http://localhost:8000/about/

Implementation Patterns

Site-wide policy on the web group. This is the intended usage. Append the middleware to web so every browser-facing route is normalized, and deliberately leave it off api so JSON clients and third-party integrations are not surprised by redirects.

Scoped rollout with route groups. If you are not ready to flip the whole site, attach the middleware to specific groups:

Route::middleware([\Fsasvari\LaravelTrailingSlash\Middleware\TrailingSlash::class])
    ->group(function () {
        Route::get('/about', [PageController::class, 'about']);
        Route::get('/pricing', [PageController::class, 'pricing']);
    });

Pair with route-level canonical tests. Because Laravel's router already matches /about and /about/ interchangeably, the only place the policy is observable is the HTTP response. Add feature tests that assert the redirect and that the canonical URL returns 200:

public function test_non_canonical_url_redirects(): void
{
    $this->get('/about')
        ->assertStatus(301)
        ->assertRedirect('/about/');

    $this->get('/about/')->assertOk();
}

public function test_query_string_is_preserved(): void
{
    $this->get('/search?q=shoes')
        ->assertRedirect('/search/?q=shoes');
}

Order it before auth/session-heavy middleware when possible. A redirect should not create a session, touch the database, or burn a rate-limit hit. In the web group, appending it places it after StartSession and VerifyCsrfToken; if that overhead matters, register it as global middleware instead and exclude non-HTML paths inside the middleware or config.

Use it together with URL::forceScheme() and TrustProxies. The redirect target is built from the request, so behind TLS-terminating load balancers you must trust the proxy headers or you will emit http:// redirects from an https:// site.

Exclusions belong in config, not in route files. Health checks (/up), webhooks, and API prefixes should be listed in the package config so operations tooling doesn't follow a 301 on a POST.

Gotchas and Tips

  • Redirect loops at the edge. Nginx, Cloudflare, or an ALB that normalizes trailing slashes will fight the middleware. If you see infinite 301s, check whether the proxy rewrites the path before Laravel sees it. Pick the enforcement point and disable the other.
  • Only redirect safe methods. A 301 or 302 on a POST causes many clients to resend as GET and drop the body. If the package does not restrict itself, wrap the call or ensure the middleware only acts on GET/HEAD. Use 308 if you genuinely need to preserve the method.
  • Signed URLs break. Laravel's URL::signedRoute() signature covers the full URL including the trailing slash. Normalizing a signed URL invalidates the signature. Exclude signature-validated routes or generate them in canonical form.
  • Static files and extensions. Requests for /favicon.ico, /robots.txt, or /storage/foo.pdf should never be redirected to /favicon.ico/. Confirm the middleware skips paths with file extensions and existing public files; if it doesn't, add them to the exclusion config.
  • Double slashes. A malformed request to //about// can produce //about// as the redirect target. Normalize the path (or collapse duplicates) before redirecting to avoid protocol-relative URL bugs.
  • Inertia, Livewire, and XHR. These clients follow redirects transparently, so users rarely notice — but the extra round trip shows up in request waterfalls and can break assertRedirect expectations in tests. It also changes the URL in the browser bar, which may be what you want for Inertia but not for internal Livewire polling endpoints.
  • Config caching. After publishing and editing the config, run php artisan config:cache in production; a stale cache is the most common reason "the exclusion I added didn't work."
  • Extension point: publish and subclass the middleware. If you need per-tenant or per-domain policies (e.g., /en/ gets a slash, /api/ does not), copy the middleware into app/Http/Middleware and register your version. Keep the package as the reference implementation rather than fighting its config.
  • Don't ship it to /api. Even a same-origin 301 can strip an Authorization header on some HTTP clients, and OpenAPI-generated SDKs often treat any 3xx as an error. Exclude the API prefix explicitly.
  • Verify with curl -I, not the browser. Browsers cache 301s aggressively; once you've hit a URL during development, the browser may keep redirecting even after you unregister the middleware. Test in a private window or with curl --location-trusted.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core