PHPackages                             step2dev/lazy-seo-redirects - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. step2dev/lazy-seo-redirects

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

step2dev/lazy-seo-redirects
===========================

Simple and safe Laravel SEO redirects with database rules, wildcard/regex matching, CSV import/export and hit tracking.

040↓100%PHPCI passing

Since May 10Pushed 1mo agoCompare

[ Source](https://github.com/step2dev/lazy-seo-redirects)[ Packagist](https://packagist.org/packages/step2dev/lazy-seo-redirects)[ RSS](/packages/step2dev-lazy-seo-redirects/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

Lazy SEO Redirects
==================

[](#lazy-seo-redirects)

Safe Laravel redirect manager with database redirects, wildcard/regex matching, CSV import/export and hit tracking.

Features
--------

[](#features)

- Exact redirects
- Wildcard redirects
- Optional regex redirects
- `301`, `302`, `307`, `308`, `410` status codes
- Query string preservation
- Redirect loop protection
- Hit counter and `last_hit_at`
- CSV import/export commands
- Laravel 11, 12 and 13 support

Installation
------------

[](#installation)

```
composer require step2dev/lazy-seo-redirects
php artisan vendor:publish --tag="lazy-seo-redirects-config"
php artisan vendor:publish --tag="lazy-seo-redirects-migrations"
php artisan migrate
```

Middleware
----------

[](#middleware)

Register the middleware where you want redirects to be resolved.

### Laravel 11+

[](#laravel-11)

```
use Step2dev\LazySeoRedirect\Http\Middleware\HandleSeoRedirects;

->withMiddleware(function ($middleware) {
    $middleware->append(HandleSeoRedirects::class);
})
```

Or use it on selected routes:

```
use Illuminate\Support\Facades\Route;
use Step2dev\LazySeoRedirect\Http\Middleware\HandleSeoRedirects;

Route::middleware(HandleSeoRedirects::class)->group(function () {
    // routes
});
```

Create redirects
----------------

[](#create-redirects)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

SeoRedirect::create([
    'old_url' => '/old-page',
    'new_url' => '/new-page',
    'status_code' => 301,
    'enabled' => true,
]);
```

Gone response:

```
SeoRedirect::create([
    'old_url' => '/removed-page',
    'new_url' => null,
    'status_code' => 410,
    'enabled' => true,
]);
```

Wildcard:

```
SeoRedirect::create([
    'old_url' => '/blog/*',
    'new_url' => '/articles',
    'status_code' => 308,
]);
```

Regex redirects are disabled by default. Enable them only when needed:

```
'regex_enabled' => true,
```

```
SeoRedirect::create([
    'old_url' => '#^old/(.*)$#',
    'new_url' => '/new/$1',
    'status_code' => 307,
    'is_regex' => true,
]);
```

Reading redirects from the database
-----------------------------------

[](#reading-redirects-from-the-database)

Use the model directly when you need redirects in your own admin panel, API, dashboard, sitemap tools or reports.

### Get all enabled redirects

[](#get-all-enabled-redirects)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$redirects = SeoRedirect::query()
    ->enabled()
    ->latest()
    ->get();
```

### Paginate redirects for an admin table

[](#paginate-redirects-for-an-admin-table)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$redirects = SeoRedirect::query()
    ->latest()
    ->paginate(20);
```

### Search redirects by old or new URL

[](#search-redirects-by-old-or-new-url)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$search = request('search');

$redirects = SeoRedirect::query()
    ->when($search, function ($query, string $search): void {
        $query->where(function ($query) use ($search): void {
            $query->where('old_url', 'like', "%{$search}%")
                ->orWhere('new_url', 'like', "%{$search}%");
        });
    })
    ->latest()
    ->paginate(20);
```

### Get the most used redirects

[](#get-the-most-used-redirects)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$redirects = SeoRedirect::query()
    ->enabled()
    ->orderByDesc('hits')
    ->limit(10)
    ->get();
```

### Get inactive redirects

[](#get-inactive-redirects)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$redirects = SeoRedirect::query()
    ->where('enabled', false)
    ->latest()
    ->get();
```

### Get one redirect by old URL

[](#get-one-redirect-by-old-url)

```
use Step2dev\LazySeoRedirect\Models\SeoRedirect;

$redirect = SeoRedirect::query()
    ->where('normalized_old_url_hash', sha1(SeoRedirect::normalizePath('/old-page')))
    ->first();
```

### Use a custom table name

[](#use-a-custom-table-name)

Publish the config and change the table name before running migrations:

```
// config/lazy-seo-redirects.php

return [
    'table' => 'seo_redirects',
];
```

Import / Export
---------------

[](#import--export)

```
php artisan lazy-seo-redirects:import redirects.csv
php artisan lazy-seo-redirects:import redirects.csv --no-update
php artisan lazy-seo-redirects:export redirects.csv
```

CSV columns:

```
old_url,new_url,status_code,enabled,is_regex
/old,/new,301,1,0
```

Config
------

[](#config)

```
return [
    'enabled' => true,
    'table' => 'seo_redirects',
    'cache_seconds' => 60,
    'preserve_query' => true,
    'wildcard_enabled' => true,
    'regex_enabled' => false,
    'allowed_status_codes' => [301, 302, 307, 308, 410],
    'security' => [
        'allow_external_destinations' => false,
        'allowed_hosts' => [],
        'block_protocol_relative_urls' => true,
    ],
];
```

Testing
-------

[](#testing)

```
composer test
composer analyse
composer format
```

###  Health Score

23

—

LowBetter than 26% of packages

Maintenance61

Regular maintenance activity

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3448603?v=4)[Yurij Finiv](/maintainers/CrazyBoy49z)[@CrazyBoy49z](https://github.com/CrazyBoy49z)

---

Top Contributors

[![CrazyBoy49z](https://avatars.githubusercontent.com/u/3448603?v=4)](https://github.com/CrazyBoy49z "CrazyBoy49z (1 commits)")

### Embed Badge

![Health badge](/badges/step2dev-lazy-seo-redirects/health.svg)

```
[![Health](https://phpackages.com/badges/step2dev-lazy-seo-redirects/health.svg)](https://phpackages.com/packages/step2dev-lazy-seo-redirects)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
