HTML Blog Code Manual & Tools
Programming

SEO-Friendly URLs in PHP: Slug Function, .htaccess, and Best Practices

SEO-Friendly URLs in PHP: Slug Function, .htaccess, and Best Practices

Dynamic PHP apps default to URLs like /article.php?id=482&cat=7. That format leaks database structure, ignores human readability, and wastes the keyword slot that search engines weight most heavily in a URL. Here is how to fix it — from a slug function to Apache rewrite rules to framework helpers.

Before and after: dynamic PHP URL versus clean SEO-friendly URL slug
The same article, before and after: a dynamic query string versus a readable, keyword-bearing slug.

What a URL Change Actually Buys You

PHP, HTML and CSS represented together
PHP generates the clean, readable URLs that users and search engines prefer.

Before touching code, it helps to know what you are optimising for. The table below shows a realistic before/after for the same article:

Before (dynamic)After (SEO-friendly slug)Improvement
/article.php?id=482&cat=7/tutorials/seo-friendly-url-in-phpKeyword in URL; readable in search snippets
/page.php?type=product&ref=DG-44/products/dg-44-widgetDescribes content; no internal architecture exposed
/results.php?q=php+url+rewrite&page=3/search/php-url-rewrite/3Shareable; crawlable if desired
/en/blog.php?year=2022&id=19/en/blog/clean-url-routingDate-independent; evergreen

The Google Search documentation on URL structure explicitly recommends using words rather than IDs in URLs and keeping the structure as simple as possible.

What Makes a URL SEO-Friendly?

Creating a Slug Function in PHP

The core of every clean-URL system is a function that converts arbitrary text into a URL-safe slug. This one handles accented characters (French, German, Spanish and others), strips HTML entities, and produces a consistently lowercase, hyphen-separated result:

function createSlug(string $string): string {
    // Remove content inside brackets
    $string = preg_replace('/\[.*\]/U', '', $string);

    // Decode HTML entities
    $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');

    // Transliterate accented characters to ASCII
    if (function_exists('transliterator_transliterate')) {
        $string = transliterator_transliterate('Any-Latin; Latin-ASCII', $string);
    } else {
        $string = htmlentities($string, ENT_COMPAT, 'UTF-8');
        $string = preg_replace('/&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);/i', '$1', $string);
    }

    // Replace non-alphanumeric characters with hyphens
    $string = preg_replace('/[^a-z0-9]+/i', '-', $string);

    // Collapse duplicate hyphens and trim
    $string = preg_replace('/-+/', '-', $string);

    return strtolower(trim($string, '-'));
}

The function uses PHP’s Intl extension (transliterator_transliterate) when available, which handles a much wider character range than regex-based approaches. If the extension is missing, it falls back to HTML entity decomposition.

Usage Examples

echo createSlug("10 Tips for Better SEO");
// Output: 10-tips-for-better-seo

echo createSlug("Guantánamo: le chauffeur de Ben Laden");
// Output: guantanamo-le-chauffeur-de-ben-laden

echo createSlug("Ärger mit Ölförderung — ein Überblick");
// Output: arger-mit-olforderung-ein-uberblick

You can debug and test your own regex patterns — including the ones used in this function — with the URL Encoder/Decoder tool, which also shows how special characters are percent-encoded in real URLs.

Configuring .htaccess for URL Rewriting

The slug function generates clean strings, but Apache needs a rewrite rule to route those clean URLs to your PHP scripts. Add this to your .htaccess file in the site root:

RewriteEngine On
RewriteBase /

# Skip existing files and directories
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# Route /category/slug to index.php
RewriteRule ^([a-z0-9-]+)/([a-z0-9-]+)/?$ index.php?category=$1&slug=$2 [L,QSA]

# Route /slug to index.php
RewriteRule ^([a-z0-9-]+)/?$ index.php?slug=$1 [L,QSA]

This requires Apache mod_rewrite to be enabled. On most shared hosting it is active by default. On Ubuntu/Debian servers: sudo a2enmod rewrite followed by a service restart.

In your index.php, retrieve the slug and query the database with a prepared statement:

$slug = $_GET['slug'] ?? '';

// Always use prepared statements — never interpolate user input into SQL
$stmt = $pdo->prepare("SELECT * FROM articles WHERE slug = :slug AND status = 'published'");
$stmt->execute(['slug' => $slug]);
$article = $stmt->fetch();

Storing Slugs in the Database

Generate the slug when an article is created and store it in a dedicated column with a unique index:

ALTER TABLE articles ADD COLUMN slug VARCHAR(255) NOT NULL;
CREATE UNIQUE INDEX idx_slug ON articles(slug);

To avoid collisions when two articles produce the same base slug, use a counter loop:

function generateUniqueSlug(PDO $pdo, string $title, string $table = 'articles'): string {
    $slug = createSlug($title);
    $original = $slug;
    $counter = 1;

    $stmt = $pdo->prepare("SELECT COUNT(*) FROM {$table} WHERE slug = :slug");

    while (true) {
        $stmt->execute(['slug' => $slug]);
        if ($stmt->fetchColumn() == 0) break;
        $slug = $original . '-' . ++$counter;
    }

    return $slug;
}

SEO-Friendly URLs in Modern PHP Frameworks

If you are using a framework, slug generation and routing are already built in:

Laravel

use Illuminate\Support\Str;

$slug = Str::slug('SEO Friendly URL in PHP');
// Output: seo-friendly-url-in-php

// Route definition
Route::get('/blog/{slug}', [ArticleController::class, 'show']);

Symfony

use Symfony\Component\String\Slugger\AsciiSlugger;

$slugger = new AsciiSlugger();
$slug = $slugger->slug('Guantánamo: le chauffeur de Ben Laden');
// Output: Guantanamo-le-chauffeur-de-Ben-Laden

WordPress

WordPress handles slugs automatically. Go to Settings → Permalinks and select the Post name option (/%postname%/). WordPress generates slugs from post titles and manages duplicates. No custom code needed.

Best Practices Checklist

  1. Keep URLs under 75 characters — shorter URLs display fully in search result snippets without truncation
  2. Place the primary keyword early in the path — the leftmost words carry more weight in most ranking systems
  3. Avoid date segments in content URLs/2022/04/my-post makes content look outdated; /blog/my-post is evergreen
  4. Use 301 redirects when changing URLs — old URLs that 404 lose their accumulated link equity permanently
  5. Implement <link rel="canonical"> if the same content is reachable via multiple URLs
  6. Enforce HTTPS — Google uses HTTPS as a ranking signal; most hosts provide free certificates via Let’s Encrypt
  7. Avoid URL parameters for navigational pages — use path segments (/blog/page/2) rather than query strings (?page=2) where possible

Frequently Asked Questions

Do hyphens or underscores perform better in URLs?

Hyphens. Google’s John Mueller confirmed in a webmaster hangout that Google treats hyphens as word separators in URLs, while underscores cause words to be read as a single token. /seo-friendly-url is seen as three words; /seo_friendly_url is treated as one string. The Google Search documentation on URL structure recommends hyphens.

Should I include the category in the URL slug?

It depends on your architecture. Including a category (/tutorials/seo-friendly-url-in-php) communicates hierarchy to crawlers and users, but makes URLs harder to maintain if you ever reclassify content — a URL change requires a 301 redirect. Flat slugs (/seo-friendly-url-in-php) are more flexible. WordPress defaults to flat slugs for posts for exactly this reason.

What happens to my SEO if I change existing URLs?

Old links and cached results will fail if you do not add 301 redirects. A 301 passes roughly the same link equity to the new URL as the old one had, and Google will re-index the new URL within days to weeks depending on crawl frequency. Never leave old URLs returning 404 — that discards the equity and breaks any inbound links.

Does mod_rewrite work on Nginx?

No. mod_rewrite is an Apache module. On Nginx, the equivalent is a try_files directive or a rewrite block in the server configuration. The PHP slug-generation code is identical regardless of web server; only the server-side routing configuration differs.

Is it worth migrating URLs on an established site?

Only if the current URLs are actively hurting you — for example, if they expose session tokens, change with every request, or are duplicated by multiple parameter combinations. For a stable site with decent rankings, the risk of a URL migration (redirect chain errors, temporary ranking dips, broken external links) often outweighs the benefit of cleaner URLs on pages that are already indexed and ranking.