Skip to content

Instantly share code, notes, and snippets.

@impressiver
Created November 15, 2013 00:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save impressiver/7477068 to your computer and use it in GitHub Desktop.
Save impressiver/7477068 to your computer and use it in GitHub Desktop.
Road to Dynamic Hybrid Web Applications

Road to Dynamic Hybrid Web Applications

Introduction

There has been a natural evolution over the past few years with regard to developing increasingly rich, desktop-like experiences in web applications. The old school way was to handle 100% of the dynamic content generation on the server and spit static HTML back to the browser. Then JavaScript grew up a bit and people slowly started to learn (begrudgingly) that it was more than just a toy. Increasingly, much of the rendering and processing was pushed to the browser, creating 'single-page web applications' that never reloaded the browser, and only used the server as an API for retrieving data. Decoupling the back-end from the front-end was fantastic in theory, but in practice it proved to be less efficient than letting the server do what it has spent the last 20 years getting good at - rendering html.

While the entire tech world has spent the last two years developing tribblous piles of client frameworks that require nothing but REST APIs and JSON responses to build sophisticated applications, some of the big players (e.g. Twitter, Facebook) have been collecting real-world data on performance of their own frameworks... and they realized that doing things the decoupled way turns out to be painfully slow right where things matter most: getting the initial page rendered. Users were stuck watching (or giving up on) a loading screen before all the awesome whiz-bang features could win them over. And, as it happens, the first few seconds can make the difference between a lifetime of engagement and a missed opportunity.

But that's not the only shortcoming of client-rendered frameworks, they fall short in other important areas too: they are difficult for bots to crawl and index (creating domain-specific solutions and SEO headaches), they require code that works on an exponentially diverse set of uncontrollable environments (os, browser, plugins, settings, etc.), and more often than not they require duplication of business logic on the client and server.

So what can we do about it? All kinds. It's a new frontier, where cowboys play. Twitter, for example, has settled on a hybrid approach whereby the initial page comes fully rendered from the server. It's cached, primed, distributed, and ready to go before your browser even knows what's going on. After the initial load, their javascript application boots up and provides "progressive enhancement" by making all further requests using AJAX API calls that return chunks of html instead of reloading the entire page (ommiting header, footer and the rest of the unmodified layout content). They actually use innerHTML to inject new content. That's right, innerHTML--So don't be scared to question standards (just make sure to test your decisions). Facebook handles updating multiple content blocks from a single request, using their own convention to identify which DOM elements should be updated from the response data.

It's all very exciting. Unfortunately, there aren't many (any?) existing agnostic frameworks that allow you to glue your favorite server-side framework to your favorite client-side framework and achieve the 'hybrid' browsing experience. So, now you are tasked with coming up with the Holy Grail; a hybrid framework or convention that is easy to understand, flexible enough to handle the common things web applications do, and (most importantly) something that won't take two years to unleash.

The Crux

Finally we get to the meat of the issue: we can define the problem and list the requirements a good solution should address. Let's break it down:

Problem

  • Traditional web applications waste time and bandwidth by re-requesting, and reloading unmodified content every time you click a link.
  • "Single-page" (a.k.a. thick-client, JavaScript heavy) webapps are slow to boot up, and much slower at producing rendered content compared to a primed server-side cache.
  • Common solutions tend naturally toward code duplication, be it through APIs that have to be maintained in parallel with their 'view' counterparts, or JavaScript 'views' that mirror the server logic.
  • While solutions that embrace node.js make for a homogenous codebase that's easier to maintain, I suspect most developers comprehend no hell worse than being forced to code entirely in JavaScript.

Rainbows and Unicorns

What would it take to make the perfect webapp?

  1. Content should be rendered immediately, no wating for JavaScript apps to boot before the user can view the basic HTML content.
  2. Progressive enhancement (loading the JavaScript webapp after the initial page renders) to ensure crawlers, browsers without JavaScript, and screen readers can still get the important content, while still providing modern browsers a more dynamic experience.
  3. Use the same URL to retrieve a full HTML page, partial HTML content, or a JSON response (depending on the context).
  4. Minimize duplication of code. For example, avoid separate client and server template languages.
  5. Language/platform agnostic. Something that could, in theory, be built into any web stack (just pick one stack, of your preference, to prototype an example implementation).

Project Requirements

  1. Pick your own development stack (client/server frameworks of your choosing)
  2. Templates should be rendered server-side (in a cacheable way) for quick first pageload.
  3. Generate URLs that any search indexer can understand. This means every page for generated content (e.g. blog entry, product, twoot, etc.) needs to have a unique URL that the server knows how to render.
  4. Progressive enhancement: after the initial page loads, subsequent requests are AJAX requests that only return the part of the page that needs updated (don't re-send/re-render the header, footer, css, js, etc. in the response).
  5. Use PushState in browsers that support it (you can assume it's supported for the sake of this project).
  6. DRY: Use the same url for a full page load and subsequent requests for the same content. HINT: This means you'll need to utilize whatever the HTTP protocol makes available in order to add context to the request.
    • How can you use one URL (e.g. "http://example.com/impressiver/") for:
      1. A fully rendered html page (profile page for 'impressiver')
      2. Just the html that sits inside the content area (without the header, footer, or other 'base' elements that exist on every page)
      3. A JSON reponse containing the view context data that would have been used to render a template, if you had made a request like #1.
    • Considerations for requests:
      • When the browser makes a request, what does the structure of the request look like?
      • After the JavaScript framework boots up, how do subsequent requests differ in a way that the server knows how to alter the response, even though the URL is the same?
      • How can you 'hijack' plain-jane links and turn them into PushState navigation entries?
    • Considerations for responses:
      • How do you notify the browser of errors/alerts (flash messages) that occur while processing the request (e.g. validation errors, "Login success" message), making sure to account for the different response types?
      • How do you handle base layout changes with responses for partial content? For example, you might have a different header/footer when logged out than when logged in. How do you communicate that the response content should be displayed in the 'logged out layout' vs. the 'logged in layout'?
      • How can you apply your modifications in a way that applies to all responses from the server (including those generated from 3rd party modules/extensions)?

Questions & Hate Mail

This is not a trick challenge, it may be a terribly written document, but it is not intended to stump or confuse. This is something I'm also working on and I'm interested in alternate creative ways you might attack the same problem. Please don't hesitate to ask questions or curse at me at any point, I'm more than happy to clarify what's in here.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment