so what I want to achieve is to make a visit to https://example.com/?slug=my-blogpost-url-slug
load as https://example.com/my-blogpost-url-slug
It's actually the other way round... the user visits https://example.com/my-blogpost-url-slug
(and should be the target of your links) and it loads as if the request is for https://example.com/index.php?slug=my-blogpost-url-slug
- which is the URL being rewritten to. Note that I've included index.php
in the rewrite, otherwise you are dependent on mod_dir making an additional subrequest for the DirectoryIndex
document.
This is called a front-controller pattern. index.php
is the "front-controller".
## remove .php n co
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
But first, you need to "correct" your rule (above) that ## remove .php n co
to first check that the corresponding .php
file exists before rewriting the request, otherwise this will conflict with the front-controller pattern that we need to write in the following section to rewrite your "pretty" URLs.
The "problem" with the above rule is that it rewrites anything that does not exist, which is basically what the front-controller needs to do.
The above should be written like this instead:
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^.]+)$ $1.php [L]
You don't need to backslash escape literal dots when used inside regex character classes as they carry no special meaning in this context. The NC
flag is redundant since the regex ^([^.]+)$
already matches both upper and lowercase letters.
The front-controller pattern
This is the bit you are really asking about...
We can avoid additional file system checks by restricting the regex that the rule matches against. ie. Just URLs of the form /my-blogpost-url-slug
where the slug cannot contain slashes or dots (which would conflict with actual files).
You can do this with an additional rule at the end of your file:
# Rewrite "/<slug>" to "/index.php?slug=<slug>"
RewriteRule ^[^/.]+$ index.php?slug=$0 [L]
The $0
backreference contains the entire URL-path matched by the RewriteRule
pattern. Note there is no slash prefix on this URL-path. Include the QSA
flag if you are expecting an additional query string on the requested URL.
When calling the document root (ie. homepage) then index.php
is called (as you state), but the slug
URL param will be omitted.
Note you do not need to repeat the RewriteEngine
directive multiple times in the file, as you have done. For readability, this should occur once at the top of the file.
Summary
The order of your rules is also incorrect. The canonical redirects should be first. For example, your rules should look like this (noting the additional points regarding the ErrorDocument
directives below):
## error documents
ErrorDocument 404 /err.php
ErrorDocument 500 /err.php
RewriteEngine On
## force https
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
## remove www
RewriteCond %{HTTP_HOST} ^www\.example\.com
RewriteRule (.*) https://example.com/$1 [R=301,L]
## remove .php n co
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^.]+)$ $1.php [L]
#### FRONT-CONTROLLER ####
# Rewrite "/<slug>" to "/index.php?slug=<slug>"
RewriteRule ^[^/.]+$ index.php?slug=$0 [L]
Aside:
ErrorDocument 404 https://example.com/err?code=404
ErrorDocument 500 https://example.com/err?code=500
You should be using a root-relative URL-path in the ErrorDocument
directive, not an absolute URL, as you have written here. This will trigger a 302 (temporary) redirect to the error document and the HTTP response status will be lost.
You should also be using the full filename, which I assume is err.php
and not rely on your other rules for this.
You then don't need to pass the HTTP status code in the code
URL parameter as this will be accessible in the $_SERVER['REDIRECT_STATUS']
superglobal in your PHP code.
So, this should be written as follows:
ErrorDocument 404 /err.php
ErrorDocument 500 /err.php
NB: You are unlikely going to catch real 500 (Internal Server) errors having defined the ErrorDocument
late in .htaccess
.
The order of these directives in the .htaccess
file does not really matter, however, it is more logical to define these first, at the top of the file, not at the end.