How I use Nginx, instead of the application, to handle redirects to a mobile version of a website.

Our company website automatically redirects visitors on mobile devices to its mobile counterpart. I was initially handling the checking/redirecting in the Ruby on Rails application using an implementation that is like the one shown in Railscast Episode 199. However, I have since made the mobile website a separate application and put it on its own subdomain, so I wanted to move the redirection logic out of the main Rails app.

Using Nginx for redirection

Visitors should not have to go to the Rails app just to be redirected away from it. By having the logic in Nginx, unnecessary request to the Rails app can be eliminated and the redirection logic can be reused in other projects that might be static HTML or built with Node.js, Python, or something else.

Defining the flow

The logic that most websites use for desktop / mobile redirection:

  • Mobile devices visiting http://www.example.com/ should redirect to http://m.example.com/
  • Desktop clients should continue to http://www.example.com/
  • Mobile devices should be allowed to the desktop website if visiting through http://www.example.com/?mobile=false
  • A cookie should be set when a mobile device chooses the desktop version to remember the choice

Redirecting to mobile website

To start, here is a very basic Nginx server configuration that can serve static pages.

server {
  listen 80 default deferred;
  server_name www.example.com;
  root /home/example/app/example_app/public;

  location / {
    index index.html;
  }
}

The redirection logic is actually quite simple, but one gotcha is that if blocks in Nginx can be unpredictable. There is even an article on the Nginx Wiki about the evils of if. Because of this, if blocks are kept out of a location context. It should only be used for setting variables, but it will also used it just once for the actual redirection.

set $mobile_request false;

if ($http_user_agent ~* '(Mobile|WebOS)') {
  set $mobile_request true;
}

if ($mobile_request = true) {
  rewrite ^ http://m.example.com$request_uri? redirect;
  break;
}

The $mobile_request variable is initialized as “false”, then is set to “true” if the requesting browser is a mobile device. Finally, if the $mobile_request variable is “true”, a redirect happens.

Setting a cookie, checking a cookie

Cookie creation regarding the visitor’s website choice should be handled in Nginx, too. However, I had some initial issues since add_header cannot be inside an if block in a server context (though it can in a location context). Because of the restriction, a $mobile_cookie variable needs to be initialized to an empty string before the if block, do the argument check, then set the variable that will always be used in add_header Set-Cookie based on the argument.

set $mobile_cookie  "";

if ($args ~ 'mobile=false') {
  set $mobile_request false;
  set $mobile_cookie  "mobile=false";
}

add_header Set-Cookie $mobile_cookie;

if ($http_cookie ~ 'mobile=false') {
  set $mobile_request false;
}

Exceptions to the redirection

One major issue that can come up from this implementation is the mobile check happens on every HTTP request through Nginx. This is not a problem for our company website since assets are hosted on Amazon S3, but if assets are hosted at /assets/ or elsewhere, an exception will need to be added to treat requests to that directory or file as non-mobile to avoid redirection.

if ($uri ~ /assets/) {
  set $mobile_request false;
}

All together

Because of the $request_uri in the redirection block, the code assumes the routes of the desktop website and mobile website will be the same, or at least mapped accordingly on the mobile side of the implementation, but that’s another post.


RSS Top

Copyright © 2014 Jonathan Underwood