Take the 2-minute tour ×
Webmasters Stack Exchange is a question and answer site for pro webmasters. It's 100% free, no registration required.

The situation

Across the entire domain, we'd like the URLs to hide file extensions and remove trailing slashes, independent of the domain name itself (as in, works on any domain).

Sample of our directory structure

We're not using index.* files except for the homepage.

  • /
    • /index.php
    • /account.php
    • /account
      • /subscriptions.php
    • /login.php
    • /login
      • /reset-password.php

The goal

Some examples of how these files might be requested, and how they should look in the browser:

  • / and index.php --> mydomain.com (literally just the bare domain name).

  • /account.php or /account/ or /account --> mydomain.com/account

  • /account/subscriptions.php or /account/subscriptions/ or /account/subscriptions --> mydomain.com/account/subscriptions

As you can see, there are several ways to access each webpage, but no matter which of the 2 or 3 ways you use to get there, it only shows the one preferred URL in the browser.

The question

How is this done with .htaccess using mod_rewrite?

I've banged my head against the wall trying to figure this out, but in general, the rewrite flow would seem to be something like this:

  1. External 301 redirect ( mydomain.com/account/ --> mydomain.com/account )
  2. Internally append .php ( mydomain.com/account --> mydomain.com/account.php )

I've been Googling this all day, read thousands of lines of documentation and config texts, and have tried several dozen times... I think more brains on this would help a lot.

UPDATE

We found an answer our question (see below).

share|improve this question
    
How would you differentiate between account.php and an account/ folder? That's what the trailing slash is for. –  Kevin C. Dec 15 '11 at 1:45
    
Good question. They are the same thing. The "account" folder rewrites to "account.php". (You'll often find this structure in Visual Studio web projects.) –  Matt Dec 15 '11 at 1:59
    
Huh? I'm sorry, that doesn't make sense to me. If there's an /account.php file, and an /account/index.php file (normally accessed simply with /account/) how do you differentiate between the two? On a filesystem, they are not the same thing. If you're bootstrapping everything through a single index.php folder, like PHP frameworks do, then maybe that make sense. Is that what you mean? –  Kevin C. Dec 15 '11 at 19:01
    
Sorry, I should have been more clear. We won't be using index files (except the homepage; but don't worry about that for this question). All the filenames are descriptive of their contents. This means, for instance, that /account/ will rewrite to /account and actually deliver /account.php, not /account/index.php. –  Matt Dec 15 '11 at 23:47
add comment

3 Answers

Why do you want to redirect /account to /account.php? In fact, the page /account only exists with Content negotiation. If you don't want it, just disable the directive.

About your two rules, I think it is straightforward:

RewriteRule ^/account/$ /account
RewriteRule ^/account$ /account.php [R,L]

It is untested, however. You can also add [R,L] to the first line, in this case, the browser will make one more redirect.

share|improve this answer
1  
In theory, that's the general idea (except we don't want to display .php in the browser), but that would only work for account.php. We're hoping for a general solution that works throughout the site. Using refiddle to test our regex's, we know that regex's like ^(.*)/$ indicate URLs with a trailing slash, but that only seems to have effect in top-level directories, not subdirectories. (Requests to subdirectories re-write with full server path in the URL and show it in the browser for some reason.) We're also certain that ^(.*)[^/]$ indicates a URL without a trailing slash. –  Matt Dec 15 '11 at 16:06
add comment

If you would like a flexible and simple way to route URL requests for your website or app, I would recommend a PHP micro-framework. Not only will you have complete control over your URL routing, but it will provide other benefits as well.

I've used the Slim Framework before, combined with the Symfony Templating Component with great results. If I were to do it again, I would probably just use the Silex Micro-framework, since it's also a part of Symfony Components.

share|improve this answer
    
We thought about something like this (thanks for the links), but our development version of the site is PHP-powered and the deployed production site is 100% static (no PHP). This .htaccess file will be fore the deployed production site. –  Matt Dec 16 '11 at 0:58
    
It would be very easy to quickly create a static site based on these micro-frameworks. The project I used them for was mostly static content, with just a splash of PHP used to bootstrap the site using the micro-framework. Depending on the size of the site, converting it to use a micro-framework might be faster than banging your head against .htaccess all day. –  Kevin C. Dec 16 '11 at 7:48
add comment
up vote 5 down vote accepted

Thanks for your time to look at the question, but we appeared to have figured it out:

Options -Multiviews -Indexes +FollowSymLinks
RewriteEngine On
RewriteBase /
DirectorySlash Off

# remove trailing slash
RewriteRule ^(.*)\/(\?.*)?$ $1$2 [R=301,L]

# rewrite /dir/file?query to /dir/file.php?query
RewriteRule ^([\w\/-]+)(\?.*)?$ $1.php$2 [L,T=application/x-httpd-php]

We have to turn off Multiviews and Indexes so the engine doesn't get confused and instead try to reference any index.* file or show a directory listing (also confusingly called an "index" with Apache...) when a directory appears to be requested.

The first redirect visibly (R=301) removes the trailing slash, and the second one internally rewrites it to the PHP (or HTML, etc.) file at hand.

This .htaccess file also supports query strings.

Update As noted in the comments below much earlier, we've switched to nginx, and this is all our conf file contains related to URL-rewriting (from my dev box):

location = / {
    index index.html;
}

try_files $uri $uri.html =404;

We've also switched from PHP to just plain HTML, but changing the extensions above should hardly make a difference, if at all.

share|improve this answer
    
In hindsight, we've switched to nginx and this whole task was SUPER-easy... –  Matt Jul 31 '12 at 12:36
    
Minor point, but you don't need to escape the forward slash (/) in the regex, as it carries no special meaning. \/ is the same as '/'. –  w3d Oct 20 '12 at 17:15
    
@w3d Good point. That's a habit from my Javascript regex'ing. –  Matt Oct 21 '12 at 3:58
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.