Grow your CSS skills. Land your dream job.

Make Client Side Data Available Server Side

Published by Chris Coyier

That would be pretty useful, right? Right now it's very common to User Agent "sniff" when you want to make a server-side decision about what to give the client. But UA sniffing has always sucked and sucks more every day. What you really want to know is stuff like "how big is the screen I'm serving to?" or "does the device I'm serving to have touch events?" - that way you can serve resources that are appropriate to those questions. Is there a way to get accurate client side information on the server side?

Why Server Side?

You might want to serve an entirely different layout or different part of a layout if you know (for sure) you are serving to a small mobile device. Or a TV. Or whatever. See this article.

Put the Data in a Cookie

I think the easiest way to make client side information available server side is by putting that data in a cookie. Cookies are sent in the headers of all requests to that domain, so the server has access to them. If that cookie has information in it about how big the browser window is, the server can use it.

Cookie Logic

In order to make the smart choices you want to make, you need this cookie to exist. If it does exist, cool. If it doesn't exist, do the bare minimum you need to do to client test, set the cookie, then refresh.

In PHP, just to illustrate:

<?php if isset(($_COOKIE['_clientInfo'])) { ?>

   <p>You have the cookie, so put in your logical stuff here and load the document as you will.</p>

<?php } else { ?>

   <p>You don't have the cookie yet, so don't load anything but the resources you need to test the client and set the cookie. Then refresh.</p>

<?php } ?>

Setting the Cookie

What kind of information might you want? Screen size likely. Probably anything that Modernizr would be able to tell you.

It's easy to set a cookie in JavaScript.

document.cookie = "cookieName=" + value;

Make the value easy for you to parse and do what you need with. Here let's make it a string of JSON. We'll make an object with all the data we want and use some jQuery and Modernizr to get the values we want to know.

var clientInfo = {
  browserWidth: $(window).width(),
  browserHeight: $(window).height(),
  flexboxSupport: Modernizr.flexbox,
  SVGSupport: Modernizr.svg
};

document.cookie = "_clientInfo=" + JSON.stringify(clientInfo);

In testing, I had some issues with Opera so switched to the jQuery cookie plugin and it was fine.

var cookieVal = JSON.stringify(clientInfo);

$.cookie("_clientInfo", cookieVal, {
  expires: 7
});

Refresh After Setting the Cookie

Now that the cookie is set, you can refresh the page so the document can actually load with all this juicy information available.

window.location.reload();

NOTE: refreshing is a bit tricky though, please read this.

Jank Alert?

If you don't like the idea of refreshing, you could write it up so you load some kind of default document at the same time you are setting the cookie. No harm there.

How Long Should the Cookie Last?

I dunno. An hour maybe? Forever? There is some chance that you catch somebody when their desktop browser is in an unusual state. Like they have it super small, and they load your page, and they then get a small screen version forever. Or an update to that browser comes out with more advanced features but your old cookie is there. Your call.

Here's an example of one hour:

var now = new Date();
var time = now.getTime();
time += 3600 * 1000; // one hour
now.setTime(time);

document.cookie = "_clientInfo=" + JSON.stringify(clientInfo) + ";expires=" + now.toGMTString();

Using the Info Server Side

Using PHP just as an example, you can easily snag the cookie and access bits of the data we saved as JSON.

<?php
  $json = $_COOKIE['_clientInfo'];
  $obj = json_decode(stripslashes($json));
  echo "<p>Browser Width: " . $obj->{'browserWidth'} . "</p>";
?>

You wouldn't just echo it out like above, you'd use some logic on that data and do whatever fancy special serving of resources and content you are going to do.

Using the Info Client Side

The point of this is server-side access, but, you have access to that cookie on the client-side too. This means, theoretically, you wouldn't need to load Modernizr again because you already have that information.

Here's a simple example that gets the data and displays a part of it:

var clientInfo = JSON.parse("<?php echo $_COOKIE['_clientInfo']; ?>");
var output = "<p>Browser Width: " + clientInfo.browserWidth + "</p>";
$("#clientInfo").append(output);

Demo

World's simplest demo just to show it's do-able.

Downsides?

I dunno. I suspect there are a bunch otherwise I would think this kind of thing would be done more often. Smart people: chime in in the comments and let me know the ups and downs.

I suspect it might complicate caching. Perhaps that much reading/sending of the cookie is an issue? What about people that don't allow cookies like this? Gotta make sure they don't get in any kind of infinite loop or have the site be unusable.

Other Things

  • The first I heard of this kind of thing being used was in Matt Wilcox's Adaptive Images where the screen resolution is saved as a cookie and used to make choices about what kind of images to use.
  • Client-Hints is also a thing that aims to give us this information without all this fancy dancing.
  • James Pearce's modernizr-server:

    The modernizr-server library is a way to bring Modernizr browser data to your server scripting environment.

  • Dave Molsen's Detector:

    Detector is a simple, PHP- and JavaScript-based browser- and feature-detection library that can adapt to new devices & browsers on its own without the need to pull from a central database of browser information.

  • Shane Gliser talks about doing this but saving to HTML5 localstorage in his book Creating Mobile Apps with jQuery Mobile

Comments

  1. My suggestion: make sure the loader (or whatever bootstrapping process you adopted) does all the sniffing necessary to know where it’s running. The browser has, anyway, a lot more info on that.

    The loader then fetches whatever assets differ from, say, resolution to resolution, features, etc.

    Much of what you describe here is the purpose of Modernizr. By classing the <html> element with the available features, you can case-load assets in CSS too.

  2. I only wish we didn’t have to code this up ourselves. It’s on my todo list to implement this on croquetscores.com so I can’t comment on pros and cons of the technique, yet.

    A technique I am using at the moment is to include info via the query string. Another idea is to post info via JSON call to API type service.

  3. Ernest Burnett
    Permalink to comment#

    Hmm…interesting!

  4. Makes a lot of sense to me. Seems like one of those things that should be standard and will probably be someday. The UA string has gotten so convoluted with browser’s faking each other, they should just identify themselves accurately and add in useful info like this.

  5. Permalink to comment#

    $obj = json_decode(stripslashes($json));

    You should never use stripslashes() unless you absolutely need to.(And if you do “absolutely need to,” then you have a problem that you should be solving.)

    stripslashes() is used to counter the effects of a horrible idea from PHP’s past: magic_quotes_gpc. Magic Quotes were a misguided attempt to improve security in situations where coders didn’t understand security. Unfortunately, they were the cause of many programming annoyances/errors (best case) and even new security risks (worst case).

    Magic Quotes have been deprecated and were removed from PHP in version 5.4. If you use an earlier version of PHP, you should drop what you are doing and go make sure Magic Quotes are turned off (see my link above for instructions; read more here.).

    If you have a situation where you don’t know if magic quotes will be turned on or not, you should check before using stripslashes().

    <?php
    if( get_magic_quotes_gpc() ){
        $json = stripslashes( $_COOKIE['_clientInfo'] );
    }
    else{
        $json = $_COOKIE['_clientInfo'];
    }
    $obj = json_decode( $json );
    
    • Permalink to comment#

      (… also, using stripslashes() on JSON when not needed can actually break the JSON string.)

  6. One thing to watch out for: “HTTP only” cookies.

    Part of OWASP, here’s the OWASP Wiki link: https://www.owasp.org/index.php/HttpOnly

    From the article: “Using the HttpOnly flag when generating a cookie helps mitigate the risk of client side script accessing the protected cookie (if the browser supports it).”

    My entire company is flipping to this.

    • Gumnos
      Permalink to comment#

      While handy and not detrimental, I’m not sure how necessary this is. The goal of HTTPOnly is to prevent private information such as session keys from leaking to other sites. If a nefarious site injects JS to access the cookie, it can skip the cookie altogether, gather the same information itself, and forward it on.

  7. tl;dr Scaling and caching gets tricky.

    I have done such a thing a before. Here’s what I learned…

    On PewResearch.org sites we created a mobile version. “Mobile” was determined by User Agent sniffing by the server. Once we knew if you were considered a mobile user or not we would set a cookie along the lines of device=mobile or device=desktop. The user agent sniffing was only done if that device cookie wasn’t sent.

    We use WordPress so I had some conditional functions to determine if the request was a desktop user or a mobile user ie. is_mobile() and is_desktop(). Mobile users would get a different style sheet, a simplified header.php and a minimal footer.php The rest of the logic for our site was basically the same. The whole goal of this was to modify a few things with the mobile version but not totally rewrite everything that goes into the desktop version. It worked quite well… but it wasn’t perfect.

    So what can go wrong?

    Scale. When the same URL produces different markup you’ll run into problems with caching (CDN, WP Super Cache etc.) You don’t want a desktop user to see the mobile view.

    We use two layers of caching: a CDN which caches the entire HTML and static assets, and WP Super Cache which reduces the number of times a certain page needs to be recreated by making a static HTML copy of the result and serving that instead.

    To solve the CDN issue I wrote a RegEx to check for the cookie on each request at the CDN level (We use EdgeCast and they have the ability to do this). If the device cookie was set to mobile, then we pass that request back to the origin server effectively bypassing the CDN. Desktop cookie users would be served the proper version. Basically mobile users request for HTML content would be handled by the origin, desktop users would get the benefit of the CDN.

    I had to modify the Wp Super Cache plugin very very slightly (like 2 lines) to make it serve different versions of the request based on the cookie value. WP SUper cache stored the static HTML files in /wp-content/cache/site-name.org/2013/04/28/slug-name/index.html and I just changed the path to /wp-content/cache/site-name.org/2013/04/28/slug-name-mobile/index.html or /wp-content/cache/site-name.org/2013/04/28/slug-name-desktop/index.html

    Eventually we were getting a lot more mobile requests and the CDN wasn’t being used for those requests so our servers bogged down and things got slow etc. Responsive design is a much better solution than what I was doing but at the beginning it did make sense to make this server side cookie switcheroo work. We were using that for a good 1.5 years. It made sense at the time.

    I’m probably not explaining it good enough because I just got back from an 8-hour drive from ConvergeSE and I’m tired.

    • I have to agree with Russell. A CDN for your dynamic content (e.g. you push everything to the edge) will probably be the biggest showstopper for this technique. Guy Podjarny noted that some of Akamai’s tech will help you work around it but I’m not entirely sure. If you can get around that particular problem your other issues are:

      The janky redirect though this can be minimized by storing results on a per unique UA basis. this is what I’m doing with Detector which is a library built to do exactly what you’re proposing.

      Constant polling on a per-request basis for certain attributes like screen width or polling per session for attributes like DPI

      Rules for handling clients that don’t support cookies or JavaScript

      A good templating set-up to limit the amount of duplicated code/content. I wrote up an example of using Mustache w/ Detector.

      That said I do like this technique and we’re using it with the West Virginia University home page and I have another proof-of-concept demo. The University of Notre Dame (switch your UA) does something similar. There are already a few server-side Modernizr implementations out there. Combined with responsive design it actually offers quite a bit of flexibility and, if you’re like me, you can do more of your conditional loading on the server rather than rely on JavaScript. I like the idea of getting mark-up right the first time.

  8. I feel with this approach you would need to not blindly rely on the information in the cookie and constantly rechecking the accuracy.

    Making assumptions about things like width and height that can change on desktop browsers rather easy.

    This technique done wrong could be more disastrous than the User Agent “sniff”. It would be rather cringe worthy if your user visited with a smaller window and your cookie with a long expiry time forced them to experience the web like
    [this example](http://m.bigpond.com/“Bigpond’s fixed 320px mobile site :'(“)

    Things like flexbox would be a great thing to know but if you are going to serve two templates up you would have to maintain both version which would sound like more headaches than what it’s worth.

    It would be interesting to see real world examples of something like this to see where the break even points as far as performance begin.

  9. That looks a bit like BEM.
    And some anti-viruses have problems with quick page reloads/redirects.

  10. Already exists, for purely server-side:

    https://github.com/jamesgpearce/modernizr-server

    Or forked by yours truly to retain those values on the client-side:

    https://github.com/aarontgrogg/modernizr-server

    Still a fair performance ping, requiring an additional, albeit minimal, roundtrip, and a couple fair-sized browser cookies.

    Also note the issue that if the device doesn’t support cookies, this becomes an infinite loop…

    But other than those two cons, the pros are pretty strong!

    Cheers,
    Atg

  11. Johan Feldt
    Permalink to comment#

    One thing to consider is the fact that the cookie will be sent along with any http request to the same domain the cookie is set for, including requests for images, stylesheets, JS-files etc. So there will be some request overhead unless you serve static content from a cookie-free domain. https://developers.google.com/speed/docs/best-practices/request

  12. Octavian Damiean
    Permalink to comment#

    If your only intention is to have a different representation of data based on the client’s display metrics and supported fearures, I don’t see the value in this.

    That’s what RWD is there for, just use CSS media queries to change how the page looks and what content is available.

    • @Octavian:

      A couple other benefits could be customizing page components based on device capabilities (i.e. if (!Modernizr.geolocation) { // include input to ask for location }) or programmatically determine proper image SRC based on screen size so you can still take advantage of browser pre-fetching and don’t have to rely on (and wait for) JS-based responsive images…

  13. Personally I try to avoid relying on sessions wherever possible, because of the scalability and caching issues Russell mentioned.

    If you want to make decisions based on client-side info, I think ajax include / conditional loading are likely to be more reliable and testable, given that they don’t depend on prior state.

    A few relevant links:

    Alex Sexton’s guide to JS app deployment, which discusses conditionally loading everything and caching separate versions of the site dependant on client-side feature set
    Alex Russell’s suggestion of using client-side feature detection to build up a server-side index of UAs and their feature sets, so you can then perform more reliable UA sniffing

  14. Maciej Baron
    Permalink to comment#

    Downsides:

    1) Request overhead<br/>
    2) You need to validate that data – although you can spoof nearly everything if you want to, editing cookies is relatively a lot easier. Obviously there is not much harm you can cause with this, however it’s another input you need to sanitise.

    Alternative solution?

    If you’re already using PHP sessions, do not create another cookie but simply add another session variable – that’s actually the most popular solution I’ve seen. This also has similar downsides, but could potentially introduce less of an overhead.

    Alternative alternative solution?

    Perform a client side redirect, once, after detecting the right parameters, and redirect to mobile.domain.com or tablet.domain.com. This assumes you have a small number of separate websites that you need to render (this should always be the case), and your responsive design takes care of the rest. This gets rid of the overhead problem, as only the request URL increases in size by a few characters.

  15. Nice article ..

  16. Permalink to comment#

    Like they have it super small, and they load your page, and they then get a small screen version forever.

    How about using window.screen.width etc. instead of current browser window width?

  17. Couple of things (even though this is just example code):

    The PHP sample you have dumps some cookie content unescaped into the page. Thar be XSS dragons here. (I.e., what if browserWidth were set to <script>alert('rawr!');</script>?) Yes, you can only inflict this upon yourself easily, but intermediaries can easily muck with cookies. Might at least be worth mentioning the risk since cookies should generally be treated as untrusted content.
    In the client side code, same escaping issue as above, but also: why bother using PHP to dump the cookie content into a client-side variable? You could just read the cookie client-side, no?

  18. Gumnos
    Permalink to comment#

    Always ensure that your site falls back gracefully if JS and/or cookies are unavailable. If not, the browser can get stuck[*] in an infinite redirect loop searching for a cookie that won’t be set.

    Also, even if you use window.screen.width to store the value instead of the browser’s much-more-changeable dimensions, those of us on laptops can have our screen dimensions change depending on whether or not we’re docked with an external monitor. Some of us dock and undock multiple times per day, so cache invalidation becomes pretty important.

    -Tim

    [*]
    While the browser usually catches this case, it’s bad UX when all you want is a little extra browser data.

  19. This kind of operation is a little easier using websockets or something like Node with socket.io. You could just load a minimal script with the websockets interface and then send info about the client to the server as data associated with an event then have the server send the appropriate data along. It’s basically the same thing as the refresh technique but a little bit smoother.

    • This sounds like a really interesting technique actually. I’d love to see a write up/demo on this Jacopo if you have the time.

    • Sounds completely unnecessary when you could just do a GET with some appropriate flags in a query string.

  20. Some related stuff elsewhere that might be of interest:

    Yiibu have a very similar experiment that they developed using PHP, called Profiler. This builds up a profile on the client-side then stores it for future use on subsequent requests, and builds up a database of tacit knowledge which is used for similar devices. They even have a really great set of slides about it that explains the process very well.

    Dave Molsen extended this idea to make Detector which uses Modernizr to parse browser and device capabilities and then categorizes devices into families.

    Another real world example by Orde Saunders inspired by Yiibu Profiler.

  21. Ferdy
    Permalink to comment#

    I’m using this technique in a project I am currently working on, and it works fine. All I am doing is setting the browser width in the cookie, and making it available to the server that way. A few things of note though:

    I don’t refresh the page. If the cookie is not set (for example on the first load), I fall back to a meaningful default
    I don’t use this technique for any serious layout changes. For that we have media queries
    I use this as a nice-to-have sub optimization

    Example: in my project, a geomap is loaded for some pages. On mobile, their usability is horrible. So on mobile, I don’t load that markup based on this cookie, instead a text string is shown with the location. On larger clients, the map is loaded. If the cookie is not set, the map is loaded.

    I think you can safely use this technique for such non-critical optimizations, but I would be very careful in making any major design decisions based on this.

  22. What if instead of refresh you would get appropriate data using ajax? (refresh as a fallback)

  23. Dominic
    Permalink to comment#

    I know, UA sniffing is not always the best way to do this but another possibility would be WURFL.

    It maps user-agent-strings to the devices and/or browsers they represent and has a huge list of capabilities that are supported by the device/browser. The APIs are quite easy to use and give you all the information you need without setting cookies or the need to refresh the page.

    • Dominic
      Permalink to comment#

      oh and it knows the screen size for a wide range of mobile devices.

  24. Pierre
    Permalink to comment#

    One of the most complete solutions for server side sniffing I came across was Dave Olsen’s Detector. It’s used on the West Virginia University’s website.

    Here’s a demo:
    http://detector.dmolsen.com/

    And the github repo:
    https://github.com/dmolsen/Detector

  25. Jimmy
    Permalink to comment#

    An other possibility is to sniff the client data and then load the rest of the page with ajax.

    The search engine issue can be solved for google at least:
    https://developers.google.com/webmasters/ajax-crawling/

  26. At times I have made server side data available to the client side. Exact opposite you are referring to here for completely different reasons, but yes, this can be very useful… thanks!

  27. I recently had to perform a hack like this;I was integrating a checkout again st a payment provider with a mobile checkout. The only downside? They wanted to know in advance when they were supposed to serve the mobile version.

    I ended up storing the information in the session to save HTTP overhead and make sure it was cleaned when the user was actually done.

  28. Andrei Pit
    Permalink to comment#

    I got a small question because i’m not entirely sure, does this affect SEO in any kind? Having to redirect makes google index after the refresh or before? My guess would be the second but i’m gonna ask anyway.

  29. How do you avoid an infinite refresh loop if the user has disabled cookies?

    • Before refreshing the page with the data you’ve collected you can see if you can read data out of the cookie. If you can’t you redirect to a page that has a request variable essentially telling your server-side stuff the user doesn’t support cookies. You server-side stuff then just serves up, most likely, all of the default assets/content. You could do something similar to catch those users and bots who disable JavaScript.

  30. Sean Ford
    Permalink to comment#

    The biggest downside for me would be the effect of a reload on analytics and simple browser history behavior (back button issues). You’d have to build in an analytics suppressor based on the existence or change of the cookie data, and ideally remove the original page from the browser history in order to maintain some semblance of normal behavior.

    It’s a good enough idea, but it certainly would take on a more structured implementation in an enterprise setting.

  31. Permalink to comment#

    DON’T DO IT, CAPTAIN.

    Each cookie set by your domain will be transferred with every browser request to that domain. All cookies will be added to the request header with every asset download and even for XMLHTTP calls.
    If you care for SEO, try G**gle PageSpeed and have a look at the corresponding warning. It will clearly tell you to work cookieless or at least reduce the amount of cookie data.

    The best compromise is:
    Use exactly one cookie containing a session ID (your server framework might provide cookie-based session handling out of the box). Let your JS framework sample all user data and transfer it to your server using XMLHTTP, javascript injection (aka JSONP, but in this case there might be no “P”) or simply image sourcing. The receiving server script stores the data together with the session ID in a server database. Now, with any successive call from your browser, the called scripts may extract the session cookie, retrieve the user data from the database and react accordingly.

    If you really care for speed you can use memcached to temporarily cache user data.

    Javascript injection and image sourcing allow sending the userdata to other domains than the one of your page URL.

    You might want to store the session ID in two ways:
    As long as the server has no user data, the ID is just the ID. Once you have received data, add a marker to the ID which can easily be removed during analysis (e.g. “4709237407” becomes “4709237407_ok”). Your javascript framework analyzes the cookie value and aborts if it is in extended syntax. Using this stragety you can minimize the amount of XMLHTTP/JSONP calls and database/cache access. Of course, this approach means you will have to write your own session handling; PHP sessions for example don’t allow modification of existing session IDs.

  32. James Pearce wrote a project called modernizr-server which runs the modernizr test and serializes the results into a cookie and then refreshes the page. In his readme file he talks about caveats. Of note is using this on a page with a form…

    “You are advised to first use modernizr-server.php on a page that is accessed by the user with a GET method. If the first request made is a POST (from a form, for example), the refresh of the page will cause the browser to ask the user if they want to immediately resubmit the form, which may confuse them.” – Pearce

    Another similar project is Simple RESS by Matt Stauffer. Its a nice little project thats worth checking out if you are interested in these techniques.

    I personally don’t use these and prefer to use more of the tools the Filament Group uses in their SouthStreet workflow, like AJAX Include Pattern and AppendAround if I need to change markup order.

    If you do use this technique where you create a cookie with client-side data about the device/browser, I think it would be useful to use Vary HTTP header in your PHP.

  33. Permalink to comment#

    Cookies could be disabled by the browser and requiring a refresh would be bad for mobile optimization.

  34. Permalink to comment#

    The numbers on the demo seemed pretty wonky. And server/client shows the same

  35. “You are advised to first use modernizr-server.php on a page that is accessed by the user with a GET method. If the first request made is a POST (from a form, for example), the refresh of the page will cause the browser to ask the user if they want to immediately resubmit the form, which may confuse them.”

    +1 katılıyorum

  36. I’m not sure if this is the correct approach.
    It’s unreliable (user can disable cookies), bad for UX (refresh) and performance (overhead of cookie-size), and search-engines might not like that solution very much (not sure about that, but seems possible).

    Sadly, I cannot think of a better solution right now.

  37. We had server-side storage of feature detection in old versions of Modernizr for Drupal but it turned out to be a gigantic pain. First, the jank of the reload stinks. Second, what about when people rotate their phone and stuff like that?

    Having briefly supported a library that offered this functionality, I can safely say it often brings a lot more trouble than it’s worth. But if you want to do RESS or something similar, I suppose its better to try working with results of feature detection server-side than to UA sniff.

  38. seanjacob

    Crazy but you could bind internal anchor clicks to submit a post form with the data in for the next page (submit form on landing page).

    I made a rough demo, I’m sure there are cons.

  39. Brad Metcalf

    I prefer using PHP sessions with HTTP Only and constant regenative IDs. One, you don’t have to worry about cookie size as it is a small footprint and all the data gets stored on your server. Two, the data is truely stored server side which makes accessing it easier. Three, no need for a javascript cookie lib that might have cross browser issues. Four, you give your users privacy from evil doers who might benifit from this information.

    To do this educate yourself on sessions. http://php.net/manual/en/ref.session.php Then make a php file you can send an ajax post to. Then make make another you can retrieve it from. And the smart thing to do on top of that would use a session token system to validate the post and retrieve requests.

    In the question of what if they have cookies disabled that is as easy as doing isset($_SESSION[‘whatever’]) on the first piece of stored data. If so do the job you need to do. If not then go with a plan b. Your php file for retrieving should have a such method and your ajax response should get a response from the php file if it couldn’t get a stored result and do your plan b as well.

  40. Hi Chris you have a problem in your demo!
    it wont stop refreshing!

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".