Weave Code
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 Missing Page Redirector Laravel Package

spatie/laravel-missing-page-redirector

Automatically redirect 404 (missing) pages in Laravel to preserve SEO during site migrations. Configure old-to-new URL redirects in a config file or implement a custom redirector (e.g., database-backed) via middleware in your global stack.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-missing-page-redirector
    

    Publish the config file:

    php artisan vendor:publish --provider="Spatie\MissingPageRedirector\MissingPageRedirectorServiceProvider"
    
  2. First Use Case: Edit config/missing-page-redirector.php and add your first redirect rule:

    'redirects' => [
        'old-url' => '/new-url',
        'another-old-path' => 'https://example.com/new-location',
    ],
    

    Test by visiting your-app.test/old-url—it should now redirect to /new-url.

  3. Key Files:

    • config/missing-page-redirector.php: Central configuration for redirects.
    • app/Providers/MissingPageRedirectorServiceProvider.php: Service provider (auto-registered).

Implementation Patterns

Core Workflow

  1. Configuration-Driven Redirects: Use the redirects array in the config file for static redirects. Ideal for small-scale migrations or one-off changes:

    'redirects' => [
        'blog/old-post' => '/blog/new-post',
        'legacy/contact' => '/contact-us',
    ],
    
  2. Database-Backed Redirects: For dynamic or large-scale redirects, create a custom redirector by implementing Spatie\MissingPageRedirector\Contracts\Redirector:

    // Example: Database-backed redirector
    use Spatie\MissingPageRedirector\Contracts\Redirector;
    
    class DatabaseRedirector implements Redirector
    {
        public function getRedirects(): array
        {
            return DB::table('redirects')->pluck('new_path', 'old_path')->toArray();
        }
    }
    

    Register it in config/missing-page-redirector.php:

    'redirector' => \App\Redirectors\DatabaseRedirector::class,
    
  3. Middleware Integration: The package auto-registers a middleware (HandleMissingPageRedirects) in App\Http\Kernel. Ensure it’s placed before HandleMissingPageRedirects in the $middleware stack if you’re customizing the HTTP kernel.

  4. Conditional Redirects: Use closures in the config for logic-based redirects (e.g., user roles, time-based):

    'redirects' => [
        'admin/old-dashboard' => function ($request) {
            return auth()->check() ? '/admin/new-dashboard' : abort(404);
        },
    ],
    
  5. Wildcard Redirects: Leverage regex or pattern matching for bulk redirects:

    'redirects' => [
        'blog/202*' => '/blog/archives/202$0', // Redirects /blog/2021/* to /blog/archives/2021/*
    ],
    
  6. Fallback Redirects: Define a default redirect for unmatched paths:

    'fallback_redirect' => '/',
    

Integration Tips

  • SEO Migration: Pair with spatie/laravel-seo-tools to update meta tags dynamically during redirects.
  • Analytics Tracking: Log redirects in HandleMissingPageRedirects middleware for post-migration analysis:
    public function handle($request, Closure $next)
    {
        if ($redirect = resolve(MissingPageRedirector::class)->getRedirect($request->path())) {
            info("Redirect triggered: {$request->path()} -> {$redirect}");
        }
        return $next($request);
    }
    
  • Testing: Use MissingPageRedirector::getRedirect() in PHPUnit tests:
    $this->assertEquals(
        '/new-url',
        resolve(MissingPageRedirector::class)->getRedirect('old-url')
    );
    

Gotchas and Tips

Pitfalls

  1. Middleware Priority:

    • Issue: Redirects may fail if HandleMissingPageRedirects runs after route resolution (e.g., after VerifyCsrfToken).
    • Fix: Ensure the middleware is the first in the $middleware stack for web group:
      protected $middleware = [
          \Spatie\MissingPageRedirector\Middleware\HandleMissingPageRedirects::class,
          // ... other middleware
      ];
      
  2. Case Sensitivity:

    • Issue: Laravel routes are case-sensitive by default. Redirects like /Old-Url won’t match /old-url.
    • Fix: Normalize paths in your redirect logic:
      'redirects' => [
          strtolower('Old-Url') => '/new-url',
      ],
      
  3. Circular Redirects:

    • Issue: Redirecting A → B and B → A creates infinite loops.
    • Fix: Use the MissingPageRedirector::getRedirect() method to validate paths before adding redirects:
      $redirector = resolve(MissingPageRedirector::class);
      if (!$redirector->getRedirect('new-path')) {
          // Safe to add redirect
      }
      
  4. Query Strings:

    • Issue: Redirects ignore query parameters (e.g., /old?param=1 won’t match /old).
    • Fix: Use regex or strip query strings in your redirect logic:
      'redirects' => [
          'old' => '/new', // Matches /old, /old?param=1, etc.
      ],
      
  5. Caching:

    • Issue: Redirects aren’t cached by default, which can impact performance for large configs.
    • Fix: Implement caching in your custom Redirector:
      use Illuminate\Support\Facades\Cache;
      
      public function getRedirects(): array
      {
          return Cache::remember('missing-page-redirects', now()->addHours(1), function () {
              return DB::table('redirects')->pluck('new_path', 'old_path')->toArray();
          });
      }
      

Debugging

  • Check Redirects: Use Tinker to inspect available redirects:

    php artisan tinker
    >>> $redirector = resolve(\Spatie\MissingPageRedirector\MissingPageRedirector::class);
    >>> $redirector->getRedirect('test-path');
    
  • Log Missing Redirects: Add logging to identify unmatched paths:

    // In HandleMissingPageRedirects middleware
    if (!$redirect = $redirector->getRedirect($request->path())) {
        info("No redirect found for: {$request->path()}");
    }
    

Extension Points

  1. Custom Redirect Logic: Extend the MissingPageRedirector class to add pre/post-redirect hooks:

    class CustomMissingPageRedirector extends \Spatie\MissingPageRedirector\MissingPageRedirector
    {
        public function getRedirect(string $path): ?string
        {
            $redirect = parent::getRedirect($path);
            if ($redirect && str_contains($redirect, 'analytics')) {
                // Add UTM parameters
                $redirect .= '?utm_source=legacy';
            }
            return $redirect;
        }
    }
    

    Bind it in AppServiceProvider:

    public function register()
    {
        $this->app->bind(
            \Spatie\MissingPageRedirector\Contracts\MissingPageRedirector::class,
            \App\Redirectors\CustomMissingPageRedirector::class
        );
    }
    
  2. Event-Based Redirects: Dispatch events before/after redirects using Laravel’s events:

    // In HandleMissingPageRedirects middleware
    event(new \App\Events\MissingPageRedirectAttempt($request->path(), $redirect));
    
  3. API Redirects: Expose redirects via an API endpoint for dynamic management:

    Route::get('/api/redirects', function () {
        return response()->json(
            resolve(\Spatie\MissingPageRedirector\Contracts\Redirector::class)->getRedirects()
        );
    });
    

Configuration Quirks

  • Environment-Specific Redirects: Use environment variables or conditional logic in the config:
    'redirects' => env('APP_ENV') === 'production'
        ? require __DIR__.'/redirects_production.php'
        : [],
    
  • Dynamic Config Loading: Load redirects from external sources (e.g., S3, remote API) by implementing Redirector:
    class RemoteRedirector implements Redirector
    {
        public function getRedirects(): array
        {
            return json_decode(file_get_contents('https://example.com/redirects.json'), true);
        }
    }
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport