Taken from this answer on Stack Overflow: .htaccess mod_rewrite: rewriting querystring to path
<IfModule mod_rewrite.c>
RewriteEngine On
# Remove index.php
RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
RewriteCond %{REQUEST_URI} !^/index\.php/admin\b [NC] # Don't mess with admin requests
RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L]
# Remove ?p=
RewriteCond %{THE_REQUEST} ^GET\ /\?(([^&\s]*&)*)p=([^&\s]+)&?([^\s]*)
RewriteRule ^(.*)$ /%3?%1%4 [L,R=301]
# Send would-be 404 requests to Craft
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(favicon\.ico|apple-touch-icon.*\.png)$ [NC]
RewriteRule (.+) index.php?p=$1 [QSA,L]
</IfModule>
This not only reformats /?p=about
as /about
, but it also preserves other parts of the query string.
For example, /?p=about&tag=stuff
and even /?tag=stuff&p=about
both redirect to /about?tag=stuff
.