Instead of making exceptions for a growing list of expected file types, it is more usual (and more flexible) to just make an exception if the file exists (or, optionally, is a directory). For example:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{QUERY_STRING} !^page
RewriteRule ^(.*?)/?$ index.php?page=$1 (L)
The above says… if the request is not a file or directory on the filesystem then proceed and rewrite the request. The request should be rewritten directly to the file that handles the request, eg. index.php
. In your example /?page=$1
is reliant on mod_dir issuing an additional subrequest for the DirectoryIndex
, ie. index.php
.
Since all your CSS, JS, images, etc. are all physical files on the filesystem then they won’t be rewritten.
Note that the regex ^(.*)/?$
, as stated in the question, is arguably incorrect. It looks like you are trying to omit an optional trailing slash from the captured subpattern. So you could request either /location
or /location/
, but only capture location
in both cases. However, that is not what this regex does. Because the preceding subpattern/quantifier (.*
) is greedy, this will also consume the optional trailing slash, so the /?
that follows doesn’t actually do anything. The regex ^(.*)/?$
is the same as ^(.*)$
(or simply (.*)
). If you wanted to exclude an optional trailing slash from being captured, you would need to make the preceding pattern non-greedy, so that it doesn’t consume the optional trailing slash. eg. ^(.*?)/?$
(as stated above). Or, use alternation, eg. ^(.*)(?:/|)$
. Or, make the preceding pattern more restrictive, so it won’t match a slash. eg. ^((a-z)*)/?$
. But note that making the trailing slash optional here, potentially creates a duplicate content issue.
Apache even provides a one-liner (part of mod_dir) that essentially does the same thing:
FallbackResource /index.php
This internally rewrites all requests for non-existent files to /index.php
. However, you would then need to check the requested URL (eg. $_SERVER('REQUEST_URI')
in PHP), rather than the page
URL param in order to route the request.
Reference:
https://httpd.apache.org/docs/2.2/mod/mod_dir.html#fallbackresource
UPDATE: Ignore all files with an ending .css
, .js
, .php
, .html
, .png
However, having said that, if you want to ignore specific file extensions, it will be more efficient to first exclude these known file types before the general filesystem check (as mentioned above). The filesystem check is relatively expensive when compared to a regex pattern match.
So, combining these two checks, you could do something like the following:
RewriteCond %{REQUEST_URI} !.(css|js|php|html|png)$ (NC)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{QUERY_STRING} !^page
RewriteRule ^(.*?)/?$ index.php?page=$1 (L)
Or, perform the file extension check in the RewriteRule
pattern (slightly more efficient – since this is processed first). For example:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{QUERY_STRING} !^page
RewriteCond %{REQUEST_URI} ^/(.*?)/?$
RewriteRule !.(css|js|php|html|png)$ index.php?page=%1 (NC,L)
The last RewriteCond
directive is then used to grab the relevant part of the URL-path (from the example in the question), which is referenced using the %1
(as opposed to $1
) backreference in the RewriteRule
substitution. (Whether you need to do this will depend on your URL structure and what your script expects.)
Depending on your URL structure, it may be enough to simply exclude URLs that have a file extension, without being specific as to the actual file extensions. In which case you can probably omit the file-check (unless you have physical files without file extensions – unlikely). For example:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{QUERY_STRING} !^page
RewriteCond %{REQUEST_URI} ^/(.*?)/?$
RewriteRule !.(a-z0-9){2,4}$ index.php?page=%1 (NC,L)
In the above example, a file extension is assumed to be a dot followed by 2, 3 or 4 alphanumeric characters, which covers .css
, .js
, .mp4
, etc.