.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?route=$1 [QSA,L]
index.php
try {
// берем переданный роут
$route = trim($_REQUEST['route']??'index');
// проверяем, если в конце слеш, то это index роут
if (substr($route,'-1') == '/') $route.='index';
// минимальная защита от инклуда неожидаемых файлов
// ограничиваем имена до символов a-b, 0-9, тире, нижнее подчеркивание и слеш
if (!preg_match('~^[-a-z0-9/_]+$~i', $route)) throw new Exception('Not allowed route');
// генерим путь к файлу
$filePath = dirname(__FILE__).'/pages/'.$route.'.php';
// если не существует выкидываем ошибку
if (!file_exists($filePath)) throw new Exception('Route not found');
// если существует, инклудим файл
include $filePath;
} catch (Throwable $ex) {
// в случае любых ошибок, показываем 404
// тут обычно делают разные типы эксепшенов и разделяют 400 и 500 ошибки
include dirname(__FILE__).'/pages/404.php';
}
структура вызова
/ => /pages/index.php
/index => /pages/index.php
/about => /pages/about.php
/user/ => /pages/user/index.php
/user/index => /pages/user/index.php
/user/hello => /pages/user/hello.php
...
/404 => /pages/404.php
/<not-found> => /pages/404.php
/user/<not-found> => /pages/404.php
Далее доработать по потребностям.
Но, я бы не советовал самому пилить, т.к. есть много всяких нюансов. Кроме того, есть куча готовых, проверенных, решений. Разве что, просто для интереса.