Forum Moderators: phranque

Message Too Old, No Replies

Change to pretty urls using mod rewrite + 301 redirects

Just spent 2 days googling this!

         

renieravin

4:57 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Here's my problem:

I have a website using ugly dynamic urls:

[mysite.com...]

I want to use this friendly url:

[mysite.com...]

Which I have accomplished using this code in my .htaccess:

RewriteRule ^product/([0-9]+)$ /product/$1/ [R]

RewriteRule ^product/([0-9]+)/$ /product.php?product=$1

MY PROBLEM:
How do I maintain my SEO rankings for the old urls? (They also have incoming links from external websites) I need the old url
[mysite.com...] to 301 redirect to
[mysite.com...] without going into some kind of an endless loop. I tried adding a 301 to the second line in .htaccess like so:

RewriteRule ^product/([0-9]+)/$ /product.php?product=$1 [R=301,L]

but that redirects to the old url, instead of the friendly url! Obviously I don't understand what's going on and it's driving me crazy.

g1smd

5:17 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



This question comes up every day, so there's a lot of prior example code and discussion to get you started.

You need a RewriteCond looking at {THE_REQUEST} so that the Rule only operates if the URL requested was as a result of a direct client request, and not as a result of a previous internal rewrite. That will fix the loop.

Be very clear that you have a
URL: www.example.com/product/1234/ - used out on the web
and a
filepath: /product.php?product=1234 - used inside your server.

Rewrites connect URL requests to internal filepaths in a silent manner.
Redirects force the browser to make a new URL request for a new URL.

.

Your [R] in your very first rule produces a 302 redirect. Add the canonical domain name to that redirect, as well as [R=301,L].

All [L] to your rewrite, to avoid other problems.

Your new redirect is exactly backwards (as you discovered). You will need a RewriteCond to look at {QUERY_STRING} and then redirect to the new format, again using [R=301,L] on the end.

You'll end up with three rules in total. The two you have now, but tidied up, and your new one (a reverse of your trial).

.

Be aware that you cannot "maintain your ranking for the old URLs". By definition, if both URL formats are listed in SERPs you would have a Duplicate Content problem. What the redirect does, is to transfer the benefits of the old URLs over to the new ones. It needs to be a 301 redirect to do that.

renieravin

6:06 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Thanks - that's what I needed!

After googling RewriteCond I came up with this which seems to work:

RewriteRule ^product/([0-9]+)$ /product/$1/ [R]

RewriteRule ^product/([0-9]+)/$ /product.php?product=$1 [L] 

RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /product\.php\?product=([^&]+)\ HTTP/

RewriteRule ^product\.php$ /product/%1/? [R=301,L] 

I don't understand what you meant here:
"Your [R] in your very first rule produces a 302 redirect. Add the canonical domain name to that redirect, as well as [R=301,L]. ".

Also, in the code I used, what does {3,9} do? It works fine, but I'd still like to know!

jdMorgan

6:07 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



For additional information, see Changing Dynamic URLs to Static URLs [webmasterworld.com] in our Apache Forum Library.

Jim

renieravin

6:16 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Thanks Jim, that's where I got my solution from actually! I took these lines and modified them:

RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\?product=([^&]+)&color=([^&]+)&size=([^&]+)&texture=([^&]+)&maker=([^\ ]+)\ HTTP/

RewriteRule ^index\.php$ http://example.com/product/%1/%2/%3/%4/%5? [R=301,L] 

jdMorgan

6:17 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Always specify [R=301] if you intend to tell search engines that this change is permanent; Otherwise, they will continue to list the old URLs as long as they find links to them from anywhere on the Web.

Always specify a canonical URL when any [R] is used, to avoid problems with your host's default ServerName.


RewriteRule ^product/([0-9]+)$ http://www.example.com/product/$1/ [R=301,L]
#
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /product\.php\?product=([^\ ]+)\ HTTP/
RewriteRule ^product\.php$ http://www.example.com/product/%1/? [R=301,L]
#
RewriteRule ^product/([0-9]+)/$ /product.php?product=$1 [L]

Jim

g1smd

6:28 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



For your first redirect, you need to change [R] to [R=301,L] and add the domain name to the target URL, as I said above.

You also need to make sure the rewrite is listed after all of the redirects.

[Jim types quicker.]

renieravin

6:38 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Now I get it. Done that - thank you! (I didn't have the domain name there cause I'm testing on localhost now and was hoping I could use the same htaccess for local and remote.)

Thanks again.

g1smd

6:47 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



The danger with not having the domain name in the target URL of any redirect rule is that:

A path on non-www redirects to a different path but still on non-www,
and
A path on www redirects to a different path but still on www,

and you'll then need a second rule to redirect non-www to www.

.

Additionally, if CanonicalServerName is ON and is non-www, it might be that a request for www is redirected over to non-www by that, before being redirected back to www by another one of your rules.

.

Those redirection chains will cause many issues all on their own.

.

With any URL request, you need to get from A to B in just one step, not have a setup where A redirects to X redirects to Y redirects to Z redirects to B.

So, whenever you set up redirects you need to test them with lots of different URL requests:
- both non-www and www, and
- both with and without index file filename (for index URLs),
- both with and without trailing / (for folders),
- both with and without port numbers,
- both with and without query strings,
- both with and without... (you get the idea)...

Use the Live HTTP Headers extension for Firefox to verify that you get from A to B in just one step for any and all requests that are redirected.

renieravin

7:21 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Now I'm getting this error:

"Redirect Loop Firefox has detected that the server is redirecting the request for this address in a way that will never complete. "

Here's my complete htaccess code:

Options +FollowSymLinks All -Indexes
RewriteEngine on
RewriteCond %{HTTP_HOST} ^example.com [NC]
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
ErrorDocument 404 /404.php

# Use PHP5 as default
AddHandler application/x-httpd-php5 .php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule ^.+\.php$ /bogusfile

RewriteRule ^product/([0-9]+)$ http://www.example.com/blogger/$1/ [R=301,L]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /product\.php\?product=([^&]+)\ HTTP/
RewriteRule ^product\.php$ http://www.example.com/product/%1/? [R=301,L]
RewriteRule ^product/([0-9]+)/$ http://www.example.com/product.php?product=$1 [L]

renieravin

7:22 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Please ignore the earlier post -

Now I'm getting this error:

"Redirect Loop Firefox has detected that the server is redirecting the request for this address in a way that will never complete. "

Here's my complete htaccess code:

Options +FollowSymLinks All -Indexes
RewriteEngine on
RewriteCond %{HTTP_HOST} ^example.com [NC]
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
ErrorDocument 404 /404.php

# Use PHP5 as default
AddHandler application/x-httpd-php5 .php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule ^.+\.php$ /bogusfile

RewriteRule ^product/([0-9]+)$ http://www.example.com/product/$1/ [R=301,L]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /product\.php\?product=([^&]+)\ HTTP/
RewriteRule ^product\.php$ http://www.example.com/product/%1/? [R=301,L]
RewriteRule ^product/([0-9]+)/$ http://www.example.com/product.php?product=$1 [L]

jdMorgan

7:24 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



For testing support purposes, you could always do something like this, with user-defined variables:


# RewriteRule .* - [E=MyDomain:www.example.com]
RewriteRule .* - [E=MyDomain:127.0.0.1]
#
RewriteRule ^bad-url$ http://%{ENV:MyDomain}/good-url [R=301,L]

and then comment-out either the first (as shown) or second rule for test or deployment... :)

(You could also use SetEnv directives to set the "MyDomain" variable, depending on your server set-up and your preferences.)

Even more sophisticated options are possible. For example, you could define a "mode" variable, set its value to either "test" or "production," and then use that variable in a RewriteCond to enable either the first or second rule shown here. You could also use that mode variable to control other aspects of adjusting your test/production environment.

Or, you could get the %{SERVER_NAME} or %{HTTP_HOST} variable in a RewriteCond, canonicalize it, and then set the result as the value of "MyDomain".

That's the beauty of mod_rewrite: It may seem cryptic at first, but it is very compact and quite powerful, allowing you to do many things to make your work easier and your site "better" in many ways.

Jim

renieravin

7:26 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Update: I changed my code to just these lines:

RewriteRule ^product/([0-9]+)$ http://www.example.com/product/$1/ [R=301,L]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /product\.php\?product=([^&]+)\ HTTP/
RewriteRule ^product\.php$ http://www.example.com/product/%1/? [R=301,L]
RewriteRule ^product/([0-9]+)/$ http://www.example.com/product.php?product=$1 [L]

And I still get the redirect loop error!

renieravin

7:41 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Update - it works if I remove the "http://www.example.com"

Confused, I am...

g1smd

7:42 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I would have lines 2 and 3 (of 4) as the first two lines.

The last line (4) is supposed to be a rewrite so must contain only a filepath, not a domain name.
If you include a domain name, it becomes a 302 redirect whether or not you add an [R].

[Ah, you fixed it while I typed.]

So, You have a redirect that takes a dynamic URL request and redirects to a static URL, on www, with a trailing slash.
You have another separate redirect that takes a URL request without a trailing slash, and redirects to a URL that does have a trailing slash, and does include www too.
Finally you have a rewrite that accepts a URL path with a trailing slash, and internally rewrites to a server path that uses dynamic variables.

That's the correct way. Canonicalise the URL requests (using redirects) first, so that all users "see" the right URL, then rewrite that URL to get the content.

Finish it all off by making sure that all of the internal links within your site link to the URL format that you want the users to see.

.

One tip. Separate the rules out with a blank line between each, and add a comment above each one to say what it does.

Final clean up (going back to your #3843243 long code snippet) is for you to place your "general non-www to www" redirect after the other two redirects and before the rewrite. Failure to do that will cause a redirection chain for non-www requests without a slash, and for non-www requests with parameters.

Place the "php rewrite" after all of the redirects otherwise you'll expose the internal filepath when a redirects kicks in after the rewrite. You need to place redirects before rewrites. Always.

Care to post your complete final code?

[edited by: g1smd at 8:21 pm (utc) on Feb. 5, 2009]

renieravin

8:13 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Hey thanks buddy, will do the cleanup - but I have a new problem.

I also have a situation where

http://www.example.com/product.php

shows a list of default products. So http://www.example.com/product/ should redirect to http://www.example.com/product.php

The code I'm using now only redirects if the parameter "?product=" is present...

g1smd

8:19 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



What is the filepath of the resource inside the server?

What is the URL you want users to 'see' and 'use' to access that content?

The URL will contain both a domain name and a filepath.

The filepath will contain only a filepath.

e.g. "I want people to use URL example.com/foo to access content located at filepath /bar in the server file system."

renieravin

8:32 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



ok.. by filepath, do you mean something like "vhosts/virtual/www" ?

renieravin

8:33 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Or do you mean the path from root like "/products/product.php" ?

g1smd

8:37 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



/products/product.php - is an example of a filepath - the location of where the content really resides.

renieravin

8:48 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



I want people to use URL
http://www.example.com/product/
to access content located at
/product.php
in the server file system.

+

I want people to use URL
http://www.example.com/product/1234/
to access content located at
/product.php?product=1234
in the server file system.

(I have achieved the 2nd part thanks to your help, it's the first that's bugging me)

jdMorgan

8:54 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



The problem is that you have added an [R=301,L] to a rule that *I did not post* that way above.

The last rule *must not* have a [R=301] flag on it, and it *must* not contain the domain name in the substitution URL.

You will do well to pay attention to every little detail when working with mod_rewrite; It is utterly unforgiving, and take down your server or wreck your search rankings with a single typo.

Go back, review my post, and fix your code. Flush your browser cache, re-test, and let us know how it goes.

Jim

g1smd

9:32 pm on Feb 5, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Please put it all together and post what you have, including all the necessary corrections noted already, before we get into anything new.

renieravin

9:33 pm on Feb 5, 2009 (gmt 0)

10+ Year Member



Hey will do - it's 3 AM here so I'm off to sleep for now. Thanks, will update this thread tomorrow.