Grow your CSS skills. Land your dream job.

Single Page Refresh for Client Side Data Server Side

Published by Chris Coyier

We recently covered how you could get client-side information and make it available server side. Do real tests on the client, like measure browser window width and do feature tests. Then save that data to a Cookie. Then next time the page is loaded, you'll have that data in a cookie.

I quite like that technique, and use it, but what if you really want to be using that info on the first page a user sees? You might need to do a page refresh, but you have to be safe about it.

You might be tempted to just test if the cookie is set, and if not, set it and reload. PHP and jQuery here used just for demo purposes, but you can (and I have) done this in any different backend language and with or without a front end library.

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

  <script>
    // Do tests, set cookie

    location.reload(true); // true means don't use cache
  </script>

<?php } else { ?>

  <!-- Proceed normally, using cookie data to make more choices -->

<?php } ?>

This is quite dangerous though. Because:

  1. A user might disallow cookies on purpose.
  2. A user might be using a browser that doesn't allow cookies.

From what I can tell, iOS app that include a WebView need to specifically turn on the ability to accept cookies, meaning the default is to not allow them.

If either of those above two things are true, you'll get into an infinite-reload situation, which is very bad.

The trick here is to approach it this way:

  1. Test if the cookie is present server-side.
  2. If the cookie isn't set, set it on the client side with the data you want.
  3. Then do another test, client side, if the browser supports cookies or not.
  4. If it does support cookies and you haven't used it yet (cookieUsed), then refresh.

Here's a complete document that shows how it could go down:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset='UTF-8'>
  <title>Client Info In Cookie</title>
  <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
</head>

<body>

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

      <p>Use the data as you see fit!</p>

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

      <p>You can use it server-side OR client-side.</p>

      <script>
        // You could use the plugin here too, but whatever this is lighter
        var clientInfo = JSON.parse('<?php echo $_COOKIE['_clientInfo']; ?>');

        output = "<p>Browser Width: " + clientInfo.browserWidth + "</p>";

        $("body").append(output);
      </script>

    <?php } else { ?>

      <!-- LOAD TESTING LIBRARY, only load when testing -->
      <script src="js/modernizr.js"></script>

      <!-- COOKIE LIBRARY -->
      <script src="js/jquery.cookie.js"></script>

      <!-- CREATE COOKIE -->
      <script>

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

        var cookieVal = JSON.stringify(clientInfo);

        // was using document.cookie, this plugin worked better
        $.cookie("_clientInfo", cookieVal, {
          expires: 7
        });
      </script>

      <!-- RELOAD PAGE if supported and not used -->
      <script>
        var cookiesEnabled = navigator.cookieEnabled ? true : false;
        if (cookiesEnabled) {
          location.reload(true);
        }
      </script>

      <p>Make sure you serve useful content here as well.</p>

    <?php } ?>

</body>

</html>

Live demo here.

You may need to mess around with that structure a little to suit your needs. The important part being that you serve useful content no matter what the result is. The worst that can happen then is that you keep trying to set a cookie that will never get set for those users in no-cookie environments. You make the call on how acceptable that is.

You could do more robust testing with something like Detector.

Comments

  1. Kris O.
    Permalink to comment#

    How does it affect statistics? I would assume analytics software or server log will count initial load as 2 page views.

    • Efraim
      Permalink to comment#

      Great for career bloggers!

    • Bryan Gruhlke
      Permalink to comment#

      I suppose you could potentially only include your tracking code (if you’re using something like Google Analytics) when the cookie is present. Not overly complicated, but would prevent extra pageviews from being logged.

    • @Bryan Gruhlke,

      Like Chris mentioned… what if Javascript is disabled on the client’s browser?

    • @Archie Makuwa

      <?php if ( ! isset($_COOKIE['_clientInfo'])) { ?>
          <meta http-equiv="refresh" content="0;url= ... ">
      <?php } else { ?>
      
    • Ops… Sorry, I think I mistakenly interpret your conversation.

    • Kris O.
      Permalink to comment#

      Conditional loading of GA script is one solution, but you cannot trick server logs. You have to remember that a lot of enterprise analytics and statistics software rely on those.

      Additionally, it might affect how bots and crawlers see the website. Unnecessary redirects are a no-no in Google’s book. This could potentially harm SEO rankings and throw errors in Webmaster Tools, Page Speed Analytics, etc.

      This whole solution seems more like a duct tape hack to a bigger problem.

  2. @AdrianRamiro
    Permalink to comment#

    Perfect example for what a good practice is Chris.

    I think, on first example starting line should be:

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

    instead of

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

  3. Permalink to comment#

    This technique reminds me of James Pearce’s Modernizr-Server

    One thing to think about would be how this works on pages that make a POST request, ex. a page with a form. I believe the 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.

    Also as Kris O. pointed out, another thing to consider is how this affects analytics and logs.

  4. Thomas
    Permalink to comment#

    I think this is a bad idea to mitigate an already small problem or poor HTML/CSS skills.
    While it might be easier to serve the right content than to be adaptive, this technique is real show stopper for mobile users.

  5. Feature detection can be really useful, for instance when doing support for customers. I would love to get a simple page which I could tell my customers to visit, like this one: Support details only with a full structured list of features found on the browser visiting the page.

  6. Permalink to comment#

    Much safer and not as complicated as it sounds:

    Deliver the cookie with your page.
    The page contains code to perform an XMLHTTP or even smarter a simple script call to your server (same domain and server name). This call contains the previously set cookie value as a GET parameter. If the browser has accepted the initial cookie it will be sent to the server in this javascript triggered call.
    The corresponding server page checks wether the GET parameter is identical to the received cookie value. If not, or if there’s no cookie at all, the browser does not support cookies.
    Let this second server page construct an appropriate reaction. If it was called as a javascript ressource it will be executed by the client anyway. This is where dynamic script tag embedding is much smarter than XMLHTTP, since the latter needs additional parsing and evaluation of the received code.

    Of course this only works with JS enabled clients.

  7. Bradley Staples
    Permalink to comment#

    One thing to note is that by doing this, you’re sending extra headers with every request from there on out, per cookie. This isn’t limited to pages, but any assets from the same domain. On site with large amounts of traffic, these extra headers (which are not uncompressed) can add up to quite a bit more if you don’t have assets separated from cookied subdomains. You can read more about it on google’s web performance tips (here’s a friendlier version of them.)

  8. Mohsen Haeri
    Permalink to comment#

    What if the first time the user visits the page, the browser window was small (like 800 * 600) and later he opens the page with a maximized window? The cookie saves 800 * 600, but now the window is maximized!

    I don’t like this technique because:
    1. As others said, The page reloads at least one time, so affects in logs and etc.
    2. As others said, If there was a post request, the browsers asks if the user wants to resend the data.
    3. The browser data saves for some days (like 7 days) and changes (width and height) might happen.

  9. CBroe
    Permalink to comment#

    I don’t like this much either, and think Rumpel’s approach is a better one (if you have to do this at all).

    What I think is especially worrisome about your approach is detecting whether cookies are enabled client-side using navigator.cookieEnabled – even if that may yield true, what if there’s some firewall or proxy that filters out cookie headers for “privacy reasons” or something like that …? We’d be right back into the infinite-reload situation, quote “which is very bad.

  10. I’ve seen this happen on CodePen and it bothers me a lot. After the first page load I feel nothing but disappointment going back to a white screen again and for what? My screen width? This technique may work, but it feels like a really bad hack with consequences. It gets much worse with bad caching and slow networks.

    In most cases there are very valid alternatives. Screen resolutions can be handled well with good CSS. If you can’t rely on media queries, you can always make the site fluid.

    When you have to deal with more than screen sizes, there’s AJAX. You can send the information you need on the server and tell the client what to do by running JavaScript, like replacing HTML or load an additional style or script. That’s what it’s for in the first place. If you prefer cookies, use cookies.

    Even when the client doesn’t support AJAX, you can still use JavaScript to handle the data and modify DOM or CSS.

    • After the first page load I feel nothing but disappointment going back to a white screen again and for what?

      Could you explain what you mean there? When do you go back to a white screen?

      Screen resolutions can be handled well with good CSS. If you can’t rely on media queries, you can always make the site fluid.

      On the main page that we use this on CodePen, “good CSS” is absolutely not enough to make the page work well on Mobile. It’s a completely different set of HTML, CSS, and JavaScript. That gives us the ability to have a page we can keep up to date that is specifically for small screen sizes, and make all the good choices we want to make according to that. For instance, Emmet is a really cool library we use in the desktop Editor. But it’s a tab-trigger thing that doesn’t make much sense on mobile. So in the mobile editor, we don’t load it all which has the benefit of keeping the page quite a bit lighter. I bet there are 50 or more similar choices we made. Forks in the road, so to speak. Rather than a single page that handles all 50+ of those forks, we can have a single fork right at the beginning.

      You’re free to not like it, but It’s a nice way to work, I think.

  11. I am a rookie in coding. A video on this tutorial would be helpful don’t you think? :)

  12. Sean
    Permalink to comment#

    To remove chance of a missing bracket in the php if statements, you can write it as such

    <?php if($condition) : ?>
        html
    <?php elseif($another_condition) : ?>
        stuff
    <?php else : ?>
        more stuff
    <?php endif; ?>
    
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".