Setting up SEO-friendly URLs for your site
SEO-friendly URL — readable, understandable page addresses without technical parameters. They affect CTR in search results (users see URL in snippet), link perception, and indirectly affect ranking.
Principles of good URLs
| Bad | Good |
|---|---|
/product.php?id=4521 |
/products/iphone-15-pro-256gb |
/cat/12/sub/45 |
/catalog/smartphones/apple |
/articles/2024/03/15/post-1 |
/blog/how-to-choose-laptop |
/page?lang=ru&id=about |
/about-company |
/Products/Laptops/Dell |
/products/laptops/dell (lowercase) |
Rules:
- Only lowercase letters
- Separator — hyphen, not underscore
- Without technical IDs (if possible)
- Transliteration or semantic English translation
- Logical hierarchy reflecting site structure
- Without unnecessary stop words:
the,and,or,for
Slug transliteration in Laravel
use Illuminate\Support\Str;
// Simple transliteration via iconv
function translit(string $text): string
{
$text = mb_strtolower($text);
$cyrToLat = [
'а'=>'a','б'=>'b','в'=>'v','г'=>'g','д'=>'d','е'=>'e','ё'=>'yo',
'ж'=>'zh','з'=>'z','и'=>'i','й'=>'y','к'=>'k','л'=>'l','м'=>'m',
'н'=>'n','о'=>'o','п'=>'p','р'=>'r','с'=>'s','т'=>'t','у'=>'u',
'ф'=>'f','х'=>'kh','ц'=>'ts','ч'=>'ch','ш'=>'sh','щ'=>'shch',
'ъ'=>'','ы'=>'y','ь'=>'','э'=>'e','ю'=>'yu','я'=>'ya',
' '=>'-','_'=>'-',
];
$text = strtr($text, $cyrToLat);
return preg_replace('/[^a-z0-9\-]/', '', $text);
}
// In model
protected static function boot(): void
{
parent::boot();
static::creating(function (self $model) {
if (empty($model->slug)) {
$model->slug = static::generateUniqueSlug($model->title);
}
});
}
protected static function generateUniqueSlug(string $title): string
{
$slug = translit($title);
$original = $slug;
$count = 1;
while (static::where('slug', $slug)->exists()) {
$slug = "{$original}-{$count}";
$count++;
}
return $slug;
}
Routing
// Nested routes for hierarchy
Route::get('/catalog/{category}/{subcategory?}', [CatalogController::class, 'show'])
->where(['category' => '[a-z0-9\-]+', 'subcategory' => '[a-z0-9\-]+']);
Route::get('/catalog/{category}/{subcategory}/{product}', [ProductController::class, 'show'])
->where(['product' => '[a-z0-9\-]+']);
Permanent slug vs. generated from title
- Fixed slug — doesn't change on title edit (SEO-preferable, no broken links)
- Automatic — updates with title (needs automatic 301-redirect from old slug)
Store slug history for automatic 301s:
Schema::create('slug_redirects', function (Blueprint $table) {
$table->string('old_slug')->primary();
$table->string('new_slug');
$table->string('model_type');
$table->unsignedBigInteger('model_id');
$table->timestamps();
});
URLs for multilingual sites
Structure options:
| Structure | Example |
|---|---|
| Subdomain | ru.example.com/products/laptop |
| Path prefix | example.com/ru/products/laptop |
| Separate domain | example.ru/products/laptop |
For Yandex, regional domains (.ru) are preferable. For Google — any option works if hreflang is set up.
Pagination
Preferred format: /blog/page/2 or /blog?page=2. Second variant is more convenient — doesn't require separate route, canonical is automatically correct.
Setup time: 1 day for slug system implementation with redirect history.







