Debug School

rakesh kumar
rakesh kumar

Posted on

Implementing Canonical URL Redirects Using Laravel Middleware

Introduction

In real-world Laravel apps, URLs often become inconsistent because user input, database values, and frontend links don’t always follow the same format. For example, one page might generate Royal Enfield while another uses royal-enfield. Browsers will encode spaces as %20, and suddenly you have multiple URLs showing the same content.

That’s where canonical URL redirects help. The idea is simple:

Pick one clean, standard URL format (lowercase + hyphens).

If a request comes in with a non-standard format, redirect it to the clean format (usually with 301 Permanent Redirect).

This ensures:

✅ Better SEO (no duplicate content URLs)

✅ Cleaner links for users

✅ Consistent routing & less 404

✅ Analytics becomes accurate (all traffic goes to one URL)

Why Canonical Redirects Matter
1) Prevent duplicate URLs for same content

Without canonical redirects, these can all show the same page:

/Ahmedabad/bike-rentals/Royal Enfield/Classic 350/12

/ahmedabad/bike-rentals/royal-enfield/classic-350/12

/ahmedabad/bike-rentals/royal%20enfield/classic%20350/12
Enter fullscreen mode Exit fullscreen mode

Search engines may treat them as separate pages → SEO ranking splits.

2) Fix %20 and ugly URLs

Spaces automatically become %20 and look unprofessional.

3) Consistent slugs across the app

If you normalize once at middleware level, the controller always receives clean values.

What a Canonical URL Looks Like

A canonical URL is the one official version of a page URL. For your route:

{city}/{vehicle_type}-rentals/{brand}/{model}/{id}
Enter fullscreen mode Exit fullscreen mode

Canonical slug rules can be:

lowercase

spaces → hyphens

keep existing hyphens (royal-enfield stays royal-enfield)

remove duplicate hyphens

trim extra hyphens from ends
Enter fullscreen mode Exit fullscreen mode

So the canonical format becomes like:

/ahmedabad/bike-rentals/royal-enfield/classic-350/12
Enter fullscreen mode Exit fullscreen mode

Where Middleware Fits

Laravel middleware runs before the controller. That makes it perfect for:


reading route params

normalizing them

redirecting to canonical URL if needed

then letting the request continue
Enter fullscreen mode Exit fullscreen mode

Key idea:
If incoming URL params ≠ normalized params → redirect(301) to normalized route.

Laravel Code: Canonical Redirect Middleware (Using Your Existing inject.notifications)

Below is the exact middleware you can use. It:

Normalizes route parameters (city, vehicle_type, brand, model)

Redirects to canonical URL (301)

Preserves query string

Continues to inject notification/currency data for logged-in users

✅ Updated Middleware: InjectNotificationData.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Helpers\UtilityFunctionsController;

class InjectNotificationData
{
    protected $utilityController;

    public function __construct(UtilityFunctionsController $utilityController)
    {
        $this->utilityController = $utilityController;
    }

    /**
     * Canonical slug normalize:
     * - lowercase
     * - spaces -> hyphen
     * - keep existing hyphens
     * - remove multiple hyphens
     * - trim hyphens
     */
    private function normalizeSlug($value): string
    {
        $value = trim(strtolower((string) $value));
        $value = preg_replace('/\s+/', '-', $value);   // spaces -> hyphen
        $value = preg_replace('/-+/', '-', $value);    // multiple hyphens -> one
        return trim($value, '-');
    }

    public function handle(Request $request, Closure $next)
    {
        /* =========================
         | 1) CANONICAL URL REDIRECT
         ========================= */
        $route = $request->route();

        if ($route && $route->getName() === 'showvechicle') {
            $params = $route->parameters();
            $keys   = ['city', 'vehicle_type', 'brand', 'model'];

            $normalized = $params;
            $changed = false;

            foreach ($keys as $key) {
                if (isset($params[$key])) {
                    $new = $this->normalizeSlug($params[$key]);
                    $normalized[$key] = $new;

                    if ($params[$key] !== $new) {
                        $changed = true;
                    }
                }
            }

            // Redirect to canonical route if anything changed
            if ($changed) {
                $url = route('showvechicle', $normalized);

                // Keep query string (if any)
                if ($request->getQueryString()) {
                    $url .= '?' . $request->getQueryString();
                }

                return redirect($url, 301);
            }

            // Ensure controller receives normalized params
            foreach ($normalized as $k => $v) {
                $route->setParameter($k, $v);
            }
        }

        /* =========================
         | 2) YOUR EXISTING LOGIC
         ========================= */
        if (Auth::check() && Auth::user()->role === 'user') {
            $user   = Auth::user();
            $vender = $user->id;

            $notificationData = $this->utilityController->getUserNotificationData($user);
            $selectedCountry  = $this->utilityController->getSelectedCountry();
            $currency         = $this->utilityController->getCurrency($selectedCountry, $vender);

            View::share('notificationCount', $notificationData['count'] ?? 0);
            View::share('allNotifications', $notificationData['items'] ?? []);
            View::share('userid', $user->id);
            View::share('domain', $selectedCountry);
            View::share('currency', $currency);
        }

        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

Route (No change needed)

Keep your route same:

Route::get(
    '{city}/{vehicle_type}-rentals/{brand}/{model}/{id}',
    [Bookingcontroller::class, 'showvechicle']
)->middleware(['inject.notifications'])
 ->name('showvechicle');
Enter fullscreen mode Exit fullscreen mode

How It Works (Example)
Incoming (bad):

/Ahmedabad/Bike-rentals/Royal Enfield/Classic 350/12

Middleware normalizes and redirects (301) to:

/ahmedabad/bike-rentals/royal-enfield/classic-350/12
Enter fullscreen mode Exit fullscreen mode

✅ Now search engines and users always land on the same canonical URL.

Top comments (0)