It’s important that public-facing websites respond to requests for both domain.tld and www.domain.tld. You can’t control what your users will type into their browsers and you never know which form of your site’s URL people will use in links that they share in email, social media, and links on their own sites. Of course, you want to be sure that your website responds even if they don’t use your preferred version of your URL.

However, it’s nearly as important that all of those requests are redirected to just one address for SEO purposes. This is known as choosing and enforcing a canonical URL. If you don’t enforce a canonical URL and a search engine indexes duplicate copies of your content, you risk diluting the authority that backlinks have given your content and you even risk incurring the dreaded duplicate content penalty. Both will impact how your content fares in search result rankings.

Though rel="canonical" and improvements to search engine algorithms have helped reduce unwarranted penalties related to this mistake, the risk of unnecessarily falling behind in the rankings is too great to ignore.

To solve that problem, many websites running on IIS make use of its built-in rewrite module to enforce a canonical domain name. Unfortunately, the most obvious way to accomplish that ends up causing trouble when you want to work with the site locally.

The IIS rewrite module has you covered

The IIS rewrite module makes it easy to use a 301 redirect to solve this problem in one fell swoop. With the rewrite module installed the IIS Manager even has a one-step wizard to make that process just about as simple as you could ask.

The IIS Rewrite module makes enforcing a canonical URL dead simple.

The IIS Rewrite module makes enforcing a canonical domain name simple.

This is the rewrite rule definition that the wizard would generate if I decided that domain.com should be my canonical domain name, for example:

<rule name="CanonicalHostNameRule1" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^domain\.com$" negate="true" />
  </conditions>
  <action type="Redirect" url="http://domain.com/{R:1}" />
</rule>

If you aren’t familiar with IIS’ rewrite rule syntax, this is basically the series of events that occurs when a request filters through that canonical domain name rule:

  • The rule runs for requests with any content at all after the first forward slash, by using a .* regular expression as the matching pattern.
  • Wrapping the regex in parenthesis makes that portion of the match (the entire URL in this case) available as a variable later.
  • If the {HTTP_HOST} portion of the URL is the domain that I chose as canonical, the negation condition is met and the rule will not be applied to the request. That means it doesn’t run for requests to my chosen canonical domain.
  • Finally, if that single negation condition wasn’t met, IIS responds with a 301 redirect to my chosen canonical domain, http://domain.com.
  • So that deep links into the site are redirected properly, the entire URL that was matched earlier is appended to that 302 redirect target by referencing the regex match captured in the first step: {R:1}.

That works great in production, but unfortunately this rule that the canonical domain name wizard creates causes some trouble when you need to work with the site locally with IIS Express.

(don’t) Redirect all the things

One of the nicest things about IIS Express is that it mimics full IIS much more accurately than the old Cassini development server bundled with Visual Studio did. That even includes processing rewrite module rules like the one that the canonical domain name wizard generates.

Since the rule that the wizard generates works by rewriting all requests not matching your canonical domain choice, local requests to the site running under IIS Express will also be redirected to the canonical domain name. That even includes when you hit F5 in Visual Studio to develop, test, or debug your site. Since the {HTTP_HOST} portion of http://localhost/ doesn’t match the negation condition, requests for localhost addresses will all be redirected to the canonical address of your site running in production.

There are several options to avoid this problem during development.

Environment-specific web.config files

Probably the most obvious workaround is to only include the canonical domain name rewrite rule in production web.config files.

I’m not a fan of that approach because it’s prone to (relatively easy to overlook) breakage if the wrong web.config ends up in the production environment. No one wants to unnecessarily risk finding out that a tiny, unnoticed mis-deployment knocked their site down a few notches in the search results, six months later.

Using configSource can mitigate that hassle/risk a bit, but using different files still makes it difficult or impossible to use many continuous deployment methods like deploying to Azure from Git.

Use web.config transforms

Since Visual Studio 2010, VS supports automatically transforming XML files, like the web.config, when you use its publish feature. Using that, you could omit the entire rule locally and just add it when publishing to production. You could do that by adding something like this in a web.release.config nested under your web.config:

<rule name="CanonicalHostNameRule1" stopProcessing="true"
      xdt:Transform="Insert">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^domain\.com$" 
         negate="true" />
  </conditions>
  <action type="Redirect" url="http://domain.com/{R:1}" />
</rule>

Adding xdt:Transform="Insert" to the rule tells Visual Studio (Slow Cheetah, really) to inject that rule element into an existing <rules> section in the base web.config.

However, that can be tricky to set up just right and even trickier to make work with continuous deployment approaches since Slow Cheetah needs to be involved to run the transformation.

Modify the rule to only redirect specific non-canonical URLs

The wizard creates a rule that matches all URLs and only negates if the request was for the correct canonical domain, but that’s not the only way it has to work. In the simple case of www vs. not-www, you could just flip things around and target requests to the non-canonical domain like this:

<rule name="CanonicalHostNameRule1" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^www\.domain\.com$" />
  </conditions>
  <action type="Redirect" url="http://domain.com/{R:1}" />
</rule>

This is a decent alternative in simple cases where you only have one domain pointed at a site and only care about redirecting one of www.domain.tld and domain.tld to the other. Doing things this way starts to be cumbersome as you add more domains that point to the site or need to redirect several host names to a central canonical address though.

Add another negation rule for localhost

Lately, I’ve been using a different approach that blends the advantages of the preceding options without the disadvantages.

Instead of tweaking the rule’s regex or managing multiple versions of your web.config, you can simply add another negation condition to the rewrite rule so that it also ignores requests to localhost:

<rule name="CanonicalHostNameRule1" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^domain\.com$" negate="true" />
    <add input="{HTTP_HOST}" pattern="localhost" negate="true" />
  </conditions>
  <action type="Redirect" url="http://domain.com/{R:1}" />
</rule>

That simple addition to the rule makes it work the same way when you’re debugging locally in IIS Express and when real users visit on a production server.

You can check that rule into source control and not worry if it gets deployed through continuous deployment or just accidentally gets copied to a production server for any reason in general.

Conclusion

Adding a negation rule for localhost has been working great for me over the past year or two. Nice, simple, and gets the job done without interfering with continuous deployment.

I do worry ever so slightly about the performance cost of adding an extra negation to every single request in production though. My understanding is that the rewrite module uses caching to make the impact of any given rule as negligible as possible and I haven’t seen any impact on my own production sites.

I’d love to know exactly how much difference the needless extra negation rule makes in production though, if anyone knows.