301-Redirect Configuration During Site Migration
When moving a site to a new URL structure, changing domain or redesign — 301-redirects preserve accumulated SEO weight of pages. Without them, search engines perceive old URLs as deleted pages, and rankings fall.
What is 301 and Why It's Important
301 (Moved Permanently) transfers 90 to 99% of PageRank to the new page. Google usually transfers rankings within 2–8 weeks after correct redirect setup. 302 (Moved Temporarily) does not transfer weight, use only for temporary moves.
Migration Strategy
Before the move:
- Crawl old site (Screaming Frog, Sitebulb) — export all indexable URLs
- Check Google Search Console — find pages with traffic and rankings
- Match old URLs with new (mapping table)
- Prioritize high-traffic pages
Mapping table:
| Old URL | New URL | Priority |
|---|---|---|
/catalog.php?cat=12 |
/catalog/smartphones |
High |
/product.php?id=4521 |
/products/iphone-15-pro |
High |
/about.html |
/o-kompanii |
Medium |
/news/2018/post-1 |
/blog/post-1-slug |
Low |
Implementation in Laravel (DB Storage)
Schema::create('redirects', function (Blueprint $table) {
$table->id();
$table->string('from_url', 2048)->unique();
$table->string('to_url', 2048);
$table->smallInteger('status_code')->default(301);
$table->boolean('is_regex')->default(false);
$table->integer('sort_order')->default(0);
$table->timestamps();
});
// app/Http/Middleware/HandleRedirects.php
class HandleRedirects
{
public function handle(Request $request, Closure $next): Response
{
$path = '/' . ltrim($request->getPathInfo(), '/');
// Exact match from Redis cache
$redirect = Cache::remember("redirect:{$path}", 3600, function () use ($path) {
return Redirect::where('from_url', $path)
->where('is_regex', false)
->first();
});
if ($redirect) {
return redirect($redirect->to_url, $redirect->status_code);
}
// Regex redirects (less performant, no cache)
$regexRedirects = Cache::remember('redirects:regex', 3600, function () {
return Redirect::where('is_regex', true)
->orderBy('sort_order')
->get();
});
foreach ($regexRedirects as $redirect) {
if (preg_match($redirect->from_url, $path, $matches)) {
$to = preg_replace($redirect->from_url, $redirect->to_url, $path);
return redirect($to, $redirect->status_code);
}
}
return $next($request);
}
}
Bulk Import from CSV
// Artisan command: import:redirects storage/redirects.csv
public function handle(): void
{
$file = fopen($this->argument('file'), 'r');
fgetcsv($file); // skip header
$batch = [];
while ($row = fgetcsv($file)) {
$batch[] = [
'from_url' => trim($row[0]),
'to_url' => trim($row[1]),
'status_code' => (int) ($row[2] ?? 301),
'created_at' => now(),
'updated_at' => now(),
];
if (count($batch) >= 500) {
Redirect::upsert($batch, ['from_url'], ['to_url', 'status_code']);
$batch = [];
}
}
if ($batch) {
Redirect::upsert($batch, ['from_url'], ['to_url', 'status_code']);
}
Cache::flush(); // flush redirect cache
$this->info('Import completed');
}
Nginx Configuration for Mass Redirects
For thousands of redirects — use Nginx map (more performant than middleware):
map $uri $redirect_to {
include /etc/nginx/redirects.map;
}
server {
if ($redirect_to) {
return 301 $redirect_to;
}
}
# redirects.map
/old-page /new-page;
/catalog.php /catalog;
Generate redirects.map script from database on deploy.
Domain Redirect
server {
listen 80;
server_name old-domain.ru www.old-domain.ru;
return 301 https://new-domain.ru$request_uri;
}
Post-Migration Check
- Screaming Frog: crawl new site, check redirect chains (no chains longer than 1 step)
- Google Search Console: monitor Coverage → Not Found (404)
- Ahrefs/Semrush: verify backlinks are correctly redirected
- Check
www.and non-www versions of domain
Setup timeline: 1–3 days depending on mapping volume and structure complexity.







