views:

360

answers:

3

It's of course nice to give users friendly URLs for their content on your site. But how best to do that? There are a lot of advantages to something like foo.com/users/alice, most importantly that you aren't cluttering up your root namespace. But I think simplicity for users trumps all that. A lot of big sites seem to agree (friendfeed, delicious, and flickr come to mind) and this question is about how to accomplish that on the server side.

Let's assume the real URL for alice is foo.com/userpage?user=alice and that if someone tries to surf to a nonexistent user page (let's say foo.com/bob) they should reach foo.com/createnew?user=bob.

The user of course should never see the ugly "real" URLs above, just foo.com/alice or foo.com/bob. And note that the root namespace is shared. For example, foo.com/help should not get translated to foo.com/userpage?user=help.

Presumably I'm asking for some simple mod_rewrite rules, but perhaps there's some completely different approach to this that I'm not thinking of. In any case, I thought it would be good to record a definitive or "best practice" solution to this common question.

PS: Feel free to comment on the merits of other alternatives like alice.foo.com or users.foo.com/alice.

PPS: I think I've seen this issue debated in other questions but it seems to be tricky to search for. Pointers welcome! As well as additional keywords to make this more searchable, of course. Keywords: userspace, global namespace, URL namespace.

+5  A: 

I would say it depends on how user centred your site is.

Sites like myspace are http://www.myspace.com/jim/ because the site entirely revolves around the user.

A blog or news site, however, where you can register but it isn't important or mandatory could benefit from

http://www.news.com.au/users/jim/

Do you think if you're doing a website with users you could benefit from the MVC design pattern, or at least a popular MVC framework which uses a router to direct URIs?

If that URI came through a Router, and then was sent to the UsersController, you could decide to either show the user's profile, or direct them to create that user. You would not need to mess around with mod_rewrite except to make one rule that directs all requests to non existent files to index.php (or whatever the default of your server side language is)

If you do want to use mod_rewrite, try these rules

RewriteEngine On
RewriteCond %{REQUEST_URI} !(home|contact|about) [NC] // this line may be incorrect
RewriteRule ^/users/([^/]+)/?$ userpage?user=$1 [NC,L]

Please note the leading Carat as suggested by Gumbo, so it only matches /users/ of the TLD only.

That will match anything like foo.com/users/bob with an optional trailing slash. It is case insensitive and will be the last rule applied.

If the request comes in and the $_GET['user'] does not exist in your DB, you could try something like this

$user = $_GET['user'];

if (!user_exists($user)) {

    header('Location: createnew?user=' . urlencode($user));
    exit();

}

Then on the createnew page, simply do something like this

<input type="text" name="username" value="<?php echo htmlspecialchars(urldecode($_GET['user'])); ?>" />

That will fill in the username automatically with the username they tried to access a profile with.

If you'd like to know more about PHP and MVC, try a Google search or ask a question here on Stack Overflow.

alex
Thanks, very helpful! Great point about MVC. For now this is just hacked up php so the mod_rewrite rules would be very helpful in the short term. I think your suggestion is a better way to do it in the long term.
dreeves
Okay, I'll see what I can do about mod_rewrite rules
alex
If you wanted to make all foo.com/alice to go userpage, you'd either need to specify what shouldn't go there (like about|contact|help) or you'd need to use a fake subdirectory such as /users/
alex
Specifying what shouldn't go there could become a maintenance problem, so unless you are committed to your 'hacked up php', maybe a MVC solution could be best. I am only learning it myself, and am finding it tricky/frustrating).
alex
I was imagining that I have (well, in fact I do have) files or directories (or previous mod_rewrite rules) on the server for all the things that should not get translated (like about|contact|help). So the tricky maintenance issue is to just make sure you don't ever create foo.com/bar on the server if bar is already a username.
dreeves
You should add an anchor for the start of the string (`^`). Otherwise `/foo/users/bar` would also be rewritten to `userpage`.
Gumbo
Thanks for that Gumbo, will do.
alex
I had a crack at adding a line to the Rewrite above, the RewriteCond. It's late here and I could very well be incorrect (I'll edit in the morning) but it should at least give you some idea. Perhaps a .htaccess expert provide some insight. Good luck with dreeves.
alex
Cool, I'm sure I can figure it out from here; thanks Alex! I think what I had in mind was RewriteCond %{REQUEST_FILENAME} !-f and RewriteCond %{REQUEST_FILENAME} !-d to check if the URL is valid as is. That way you don't have to hardcode the help|about|contact.
dreeves
I might be wrong, but doesn't the !-f and !-d mean 'If not a real file or directory, then run RewriteRule'.
alex
I think that's right. I took a stab at an answer based on that idea below...
dreeves
Ah, so your folders are actually called help, about, home and such? That will work fine then :)
alex
A router has nothing to do with MVC. Yes, many "MVC frameworks" have this kind of routing, but they're two unrelated principles.
Bart van Heukelom
@Bart.. yeah that is true. But majority of MVC frameworks and implementations have some kind of router that would be useful in this circumstance.
alex
+2  A: 

The following rules rewrite a URL of the form foo.com/bar to foo.com/userpage?user=bar conditional on bar not already being a file or directory on the server. Put the following in .htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/?$ userpage?user=$1 [NC,L]
</IfModule>

As in Alex's answer, the userpage script should redirect to createnew if the user doesn't exist:

$user = $_GET['user'];
if (!user_exists($user)) {
  header('Location: createnew?user=' . urlencode($user));
}

(As Knuth says, beware of bugs in the above code -- I have only proved it correct, not tried it. I'll update this answer when I've actually confirmed it works.) PS: CONFIRMED!

dreeves
Hope it works! Remember you'll have to code a user_exists() function!
alex
A: 

Dreeves,

Thank you very much for your code, that works like a charm.

I have just one question left if you don't mind. I'm trying to do the same as the topic starter, but I have also pages for each user: Video, Audio, Images.

I was thinking to have something like: http://www.mydomain.com/username/video

And in the PHP code something like: userpage.php?user=test&page=video

I tried adding userpage?user=$1&page=$2 to the RewriteRule but I get a 404 error.

Thanks in advance.

moonwalker