A readable URL can explain a page's subject, but it does not guarantee search rankings. Do not rename working, indexed addresses simply to make them look cleaner. Designing slugs for a new PHP application and migrating an established site's URLs are different tasks.
A small Apache rewrite example
This example assumes Apache 2.4, enabled mod_rewrite, and the necessary AllowOverride permission. It belongs in a web-root .htaccess file; adapt it for a subdirectory or an existing router. Do not put LoadModule in .htaccess. Module loading is a server-configuration responsibility.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^blog/([a-z0-9]+(?:-[a-z0-9]+)*)/?$ blog.php?slug=$1 [END]
A request for /blog/php-arrays is handled internally as blog.php?slug=php-arrays. This is not an external redirect: the browser address stays unchanged. Because the target includes a query string and QSA is absent, the incoming query string is not appended. A supplied ?slug=another-post therefore cannot override the path-selected value through this rule. Tracking parameters are dropped too; decide explicitly how to support them if needed.
Validate the request in PHP as well
A rewrite rule does not secure database access. The handler can also be requested directly, so validate its input. This example assumes a PDO connection from trusted application configuration and a posts(slug, title, status) schema; adapt the table to your application.
<?php
// $pdo: the application's PDO connection using utf8mb4.
$slug = $_GET['slug'] ?? '';
if (!is_string($slug) || strlen($slug) > 160
|| !preg_match('/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/', $slug)) {
http_response_code(404);
exit('Page not found.');
}
$stmt = $pdo->prepare(
'SELECT title FROM posts WHERE slug = :slug AND status = :status LIMIT 1'
);
$stmt->execute(['slug' => $slug, 'status' => 'published']);
$post = $stmt->fetch(PDO::FETCH_ASSOC);
if ($post === false) {
http_response_code(404);
exit('Page not found.');
}
echo htmlspecialchars($post['title'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
For a multilingual system, include the language in the lookup and design uniqueness around the language/slug pair. Draft and scheduled content must follow the application's publication rules. A clean-looking URL does not enforce those rules.
Generate a slug without breaking Turkish characters
Use an explicit character map instead of a replacement pattern that maps letters incorrectly. This function is for new records, not for regenerating established URLs. It is not a general transliteration solution for every writing system.
<?php
function makeSlug(string $title): string {
$text = strtr($title, [
'Ç' => 'C', 'Ğ' => 'G', 'İ' => 'I',
'Ö' => 'O', 'Ş' => 'S', 'Ü' => 'U',
'ç' => 'c', 'ğ' => 'g', 'ı' => 'i',
'ö' => 'o', 'ş' => 's', 'ü' => 'u',
]);
$slug = trim((string) preg_replace('/[^a-z0-9]+/', '-', strtolower($text)), '-');
if ($slug === '' || strlen($slug) > 160) {
throw new InvalidArgumentException('Slug is empty or too long.');
}
return $slug;
}
echo makeSlug('İçerik Yönetimi: Çığ, Şube ve Ölçü');
// icerik-yonetimi-cig-sube-ve-olcu
Identical titles can produce identical slugs. An application-level existence check alone does not prevent a concurrent insert race; use an appropriate unique index and conflict handling. Resolve conflicts for new records through a defined suffix or identifier policy.
Test address behaviour before publishing
Check that a valid slug selects the correct post and an unknown one returns an actual 404 status. An array input such as slug[]=x should produce the error page, not a database exception. Choose one preferred trailing-slash form. If you genuinely migrate a URL, redirect the old address to its corresponding replacement with 301 rather than sending every old page to the home page. Build canonical URLs from a trusted configured origin, not an unchecked request Host header.
References: Apache RewriteRule flags and PHP strtr.
Related Articles