I recently found myself in need of a way to serve slightly different instances of a web application depending on the user visiting the site. I wanted all of them to be served on the same address so that it would be as transparent as possible to the user. The solution I came up with was to use a URL parameter and a cookie to route the user to the correct instance.

The problem

You might want to do this for a lot of different reasons. The most common one is probably to serve different languages or themes. Another reason could be to test alternative versions of a feature (A/B testing). In my case, I wanted to serve different versions of my personal website to different potential employers depending on the job offer I was applying for. The real question was how to do this without creating a new subdomain or a subpath for each instance.

The solution

Let’s say you have two different versions of your website, version A and version B. You want to serve version A by default and version B when the user visits https://yoursite.com/?version=B.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Version A</title>
    </head>
    <body>
        <h1>Welcome to Version A</h1>
    </body>
</html>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Version B</title>
    </head>
    <body>
        <h1>Welcome to Version B</h1>
    </body>
</html>

They are both running on the same machine on different ports, and you are using Nginx as a reverse proxy. Here is how you can do it:

upstream versionA {
	server 127.0.0.1:4000;
}

upstream versionB {
	server 127.0.0.1:4001;
}

# Map to determine backend based on the version
map $backend_version $upstream {
	A       versionA;
	B       versionB;
	default versionA;
}

server {
	listen 80;
	server_name yoursite.com;

	location / {
		# Default backend version
		set $backend_version A;

		# Check if the "version" parameter is in the URL
		if ($arg_version) {
			set $backend_version $arg_version;
			add_header Set-Cookie "version=$arg_version; Path=/";

			# Redirect to remove the URL parameter
			return 302 $uri;
		}

		# Check if the "version" cookie exists and override backend version
		if ($http_cookie ~* "version=(\d)") {
			set $backend_version $1;
		}

		# If the cookie does not exist, set it to the backend version
		if ($http_cookie !~* "version=(\d)") {
			add_header Set-Cookie "version=$backend_version; Path=/";
		}

		# Route based on the determined backend version using the map
		proxy_pass http://$upstream;
	}
}

The ports, version names and domain are just examples. You should replace them with your actual configuration.

This configuration should be placed in the nginx /etc/nginx/sites-available directory and symlinked to /etc/nginx/sites-enabled. It will route the user to the correct instance based on the version URL parameter. If the parameter is not present, it will check if the version cookie is set. If it is not, it will set it to the default version. The cookie will be set for the root path of the site so that it is available on all pages.

Conclusion

When the user visits https://yoursite.com/?version=B, they will be redirected to https://yoursite.com/ and served version B. The cookie will be set, and they will continue to see version B until they clear their cookies. The routing is both transparent and persistent.

This solution is simple and can be easily extended to serve more versions or different types of content. It is also a good way to test new features or versions of your website without affecting all users. It is a powerful tool to have in your toolbox.