Cesar Couto
Back to Blog

Moving Beyond .htaccess: A Simple and Flexible Multi-Purpose Router

February 1, 2025 4 min read

Traditionally, I managed routes via .htaccess in my Apache servers, including some websites that have been running for over 15 years. To improve flexibility and ease of maintenance, I decided to move all routes to a self-made basic router wrapper, without relying on frameworks or packages. This approach allows me to update and extend it quickly while ensuring compatibility with various web servers, including Apache, Nginx, and Caddy.

Why Change the Approach?

Even though I have experience managing routes through Apache's configuration files, I found that every change required modifying the server setup. This approach, while functional, became cumbersome over time. By moving the routing logic into a single project file, I now handle all routes directly within the project itself, making the system future-ready and significantly easier to update. This eliminates the need for constant server configuration edits, ensuring smoother and more maintainable routing. However, servers still need to be configured to redirect all requests to index.php to function correctly.

Key Features

Multi-Language Support
The router supports multiple languages right out of the box. For example, if a URL looks like /en/about/, it automatically detects en as the language and adjusts the routing accordingly. Adding a new language is as simple as updating an array in the configuration.

Flexibility for Future Enhancements

  • Dynamic Routes: While the current version handles static routes, it’s designed so you can easily extend it to support dynamic routes as your project grows.
  • Caching Improvements: Full-page or sectional caching isn’t built in, but the architecture allows you to integrate caching mechanisms without overhauling the routing logic.

Easier Maintenance and Updates
By keeping the routing logic inside the project, I can update and control routes directly through code rather than relying on server configuration files. Being framework- or package-agnostic means all changes can be made within a single PHP file, without the need for extra steps or even a build tool. This change streamlines the process and makes it simpler to modify routes as your project evolves. 

declare(strict_types=1);

define('BASE_PATH', __DIR__);
define('SECURE_ACCESS', true);

// Config
$additional_languages = ['en'];
$lang      = 'pt';
$lang_url  = '/';
$routes = [
    '/' => ['page_index.php', 'homepage'],
    '/menu/' => ['page_menu.php', 'menu'],
    '/orders/' => ['page_orders.php', 'orders'],
    '/contacts/' => ['page_contacts.php', 'contacts'],
    '/privacy/' => ['page_privacy.php', 'privacy'],
];

// Extract language from the route (e.g., /en/about/ -> 'en')
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/';
$uriParts = array_filter(explode('/', trim($requestUri, '/')));
if (!empty($uriParts[0]) && in_array($uriParts[0], $additional_languages)) {
    $lang = $uriParts[0];
    $lang_url = '/' . $lang . '/';
    $requestUri = substr($requestUri, strlen($lang_url) - 1);
}

// Include the language file
include_once "lang/lang_{$lang}.php";

// Lookup the route (default to 404)
[$page, $current_page] = $routes[$requestUri] ?? ['page_404.php', null];
if ($page === 'page_404.php') {
    http_response_code(404);
}

// Include the requested page
require_once BASE_PATH . '/' . $page;

How It Works

The router performs a few key functions:

  • Extracting the Request URI: It uses PHP’s built-in functions to safely extract the path from the URL.
  • Language Detection: If the URL contains a language code (like /en/about/), the router automatically adjusts to serve the appropriate language.
  • Route Matching: It looks up the route in a predefined array and includes the corresponding PHP page. If no match is found, a 404 response is returned.

Since the webserver must still be configured to send all requests to index.php, this router doesn’t replace the server-level routing entirely—it simply moves the route definitions into the project for easier management.

Conclusion

This tiny homemade multi-language router is all about making route management easier and more flexible. By moving routing logic into the project, you gain the ability to update routes quickly and directly through your code, without the need to modify server configuration files every time. This approach also provides greater flexibility regarding which servers you can install your projects on, making it a practical choice for various environments. Sometimes, the simplest solutions work best for smaller projects and websites, and this kind of setup is likely to remain reliable for many years to come.

Share this blog
Instagram