Mobile visitors shouldn’t have to hit your desktop web application just to be redirected to your mobile web application. You can use Nginx to handle the redirect before it spins up all the resources of your web application.
Where I work, our desktop application automatically redirects visitors on mobile devices to its mobile counterpart. It was initially handling the checking and redirecting in the Ruby on Rails application using an implementation that was like the one shown in Railscast Episode 199. However, we 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 so mobile users never hit the desktop application.
By having the mobile redirection logic in Nginx, unnecessary requests to the Rails app can be eliminated, and it could be reused in other projects that might be static HTML or built with Node.js, Python, or something else.
The logic that most websites use for desktop and mobile redirection is:
http://www.example.com/
should redirect to http://m.example.com/
http://www.example.com/
http://www.example.com/?mobile=false
We will use this very basic Nginx server configuration that can serve static pages as our starting point.
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 should be kept out of the location context and should only be used for setting variables. However, we will also use if
one more time 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” and then set to “true” if the requesting browser is a mobile device. Finally, if the $mobile_request
variable is “true”, a redirect happens.
Cookie creation regarding the visitor’s website choice can be handled in Nginx, too. However, I had some initial issues since add_header
cannot be inside an if
block in a server context. It can be used in an if
block in a location context, but again, you shouldn’t use if
in a location context.
Because of the restriction, a workaround is to initialize a $mobile_cookie
variable 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;
}
An issue that can arise from this implementation is that the mobile check happens on every HTTP request through Nginx.
This is not a problem for us since static assets are hosted on Amazon S3, but if static assets are hosted with your application, you will want to add an exception to treat requests to your assets directory or file as non-mobile to avoid redirection.
if ($uri ~ /assets/) {
set $mobile_request false;
}
server {
listen 80 default deferred;
server_name www.example.com;
root /home/example/app/example_app/public;
## Start mobile redirection code
set $mobile_request false;
set $mobile_cookie "";
if ($http_user_agent ~* '(Mobile|WebOS)') {
set $mobile_request true;
}
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;
}
## Add redirect exceptions here
if ($mobile_request = true) {
rewrite ^ http://m.example.com$request_uri? redirect;
break;
}
## End mobile redirection code
location / {
index index.html;
}
}
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.