Illustration by

Let Links Be Links

The concept of the web as an application platform has never been more popular, but the tools used to create these so-called “web apps” are still fraught with pitfalls that are often ignored or misunderstood. Single-page web app frameworks have gained traction because they can easily be used to create fast, complex applications that feel much more solid and interactive than traditional websites. But this benefit, and the changes in mindset and development practices that accompany it, comes at the cost of basic browser functionality that web developers sometimes take for granted.

Article Continues Below

JavaScript can be fragile#section1

With vendors making it increasingly difficult to disable, we can get lulled into thinking that we don’t need to provide a fallback for users whose browsers don’t execute JavaScript. But explicitly choosing to disable JavaScript is far from the only reason a user’s browser might not run it. Government Digital Service (GDS), the team that maintains the UK government website, found that, out of every 500 visitors to GOV.UK, five did not receive JavaScript, but only one had JavaScript explicitly disabled. The other four could be missing out on JavaScript for any of several reasons: an overzealous corporate proxy server; a request for JavaScript timing out due to high latency; or even an unnoticed syntax error.

Furthermore, JavaScript—unlike CSS and HTML—does not degrade gracefully. This means that if developers use a single ES6 syntax feature, or even make a single standard library function call without checking that the function has been defined first, their JavaScript could either stop running midway through execution or not run at all. When JavaScript is used to enhance websites, this doesn’t matter so much—visitors can still follow links and submit forms and generally use the web as intended. But when JavaScript is a requirement, anyone using even a slightly older browser is likely to get a blank page—and no explanation of what to do about it.

Semantic structure is still important#section2

As designed by Tim Berners-Lee in 1993, HTML defined a common structure for the mesh of interconnected documents we now know as the web. The semantic meanings imbued in this common structure provide machine-readable context for the information contained in a web page. In practical terms, this extra information enables web browsers to enhance the user experience. For example, a web browser can implement a way to add events defined with time elements to a user’s calendar; a screen reader can read through a list differently than it would a paragraph. The difference between a list and a paragraph is clear to human viewers of a document; the common structure provided by HTML makes it clear to computers, too.

The semantic meaning behind HTML sets the web apart from native application environments like Cocoa, Windows Presentation Foundation, and Qt. Structured information matters more to the web because of the diverse ways in which it can be accessed. When I create an iPhone application, I can safely assume that every person using that application will use it in a similar way. The information my app presents will always be presented in much the same way, and I will always have complete control over that presentation. Even if someone using my app interacts with it through VoiceOver (Apple’s assistive technology for people with vision impairments), they still interact with the application in a similar way to a sighted user: by tapping around on the screen. They just happen to be hearing the text instead of seeing it.

The web doesn’t work that way. Websites aren’t viewed solely through web browsers. People consume websites through apps like Pocket or Instapaper, which try to use the structured information of a web page to extract its relevant content. A browser on a smartwatch might ignore your layout and present your information in a way that’s more suitable for a one-inch screen. Or—who knows?—your website might be used through some future device that will transform the information into thoughts beamed directly into a user’s brain. Even web screen readers don’t work like VoiceOver does on an iPhone, reading out the text in the order it’s laid out under a user’s finger. Web screen readers read through the whole document, ignoring layout, and infer meaning from the standardized semantic definitions of HTML tags. A simple example of when semantics like this matter is the recently introduced main element, used to define the main part of a document. To a sighted user viewing your website through Google Chrome, whether you use <main> or <div id="main"> makes no difference. To someone using another web client, though, such as a screen reader or Instapaper, the meaning implied by the main element is very important to their software in helping them navigate your document.

Developing an application for the web, therefore, is not as simple as developing for a native platform. Making sure it works the way we want it to in the five major browsers and pushing it out isn’t good enough for the web. We need to test our work in screen readers. We need to review our markup to make sure it provides as much semantic metadata as possible—not just for today’s web clients, but for tomorrow’s as well.

Single-page web app frameworks#section3

When using “single-page web app” frameworks like Angular and Ember, the trend is to treat websites like native apps, with little regard for the nuances that make the web unique. Developers make assumptions about their users that can seriously damage the experience of people who don’t meet those assumptions. As an example of what this mindset can result in, consider the markup for a login button (since changed) that I recently found on Patreon’s site:

<span class="patreon-button sub-section navigation-active" data-ng-click="triggerChangeWindow(navigation.login_url)">Log In</span>
Example of a login button that once appeared on Patreon’s site
Patreon’s fairly standard login button acts just like a link. No need for special JavaScript here.

This link works fine for me in Safari, but in any environment other than a current mainstream browser, this button is totally useless. Let’s say we have a hypothetical smartwatch browser called WatchBrowse. Maybe it displays a list of links for the user to navigate through because this particular smartwatch doesn’t have a cursor that can interact with the page. Because HTML defines a standard way to create links on a web page (the a element), WatchBrowse could theoretically just list every a tag on the page with its href attribute and content—until a site like Patreon comes along and decides to shun web standards in favor of reimplementing basic browser functionality from scratch.

If Patreon had used an a tag instead of a span, WatchBrowse could perhaps find the link and display it in the list. When a user selected the link, it could simulate a click event instead of just using the href attribute. But what about functionality that requires the browser to know where the link is going to lead ahead of time? A browser extension might allow you to search links on a page by their href values, which would be useful if you wanted to quickly find where somebody links to their Twitter account, for example. Firefox shows where a link is going to take me when I hover over it. When link href attributes are no longer static values but are, instead, decided by arbitrary JavaScript in click handlers, these helpful features are no longer possible.

Patreon’s site is built with Angular, and while Angular is not at fault here per se, the mentality of treating HTML as a view layer that goes along with using these frameworks probably contributed to Patreon’s poor decision.

What if we created the same link the way the framework developers recommend in their documentation? A more standard way to make a link in Angular might look like this:

<a ng-href="/login">Log In</a>

When rendered into the DOM by client-side JavaScript, that snippet turns into this:

<a ng-href="/login" class="ng-binding" href="/login">Log In</a>

Ember handles this similarly. A link is defined in an Ember template like so:

{{#link-to sessions.new}}Log In{{/link-to}}

And when it’s rendered into the DOM, it becomes this:

<a id="ember-563" class="ember-view" href="/sessions/new">Log In</a>

Ember and Angular then intercept the link’s click event to render the new content without reloading the page. Crucially, though, if the click event were never fired and the browser loaded the value of href, there would be no visible difference to the user other than an extra page reload, because Ember and Angular by default don’t try to reinvent the wheel by defining their routing in terms of URLs.

In their current forms, however, Ember and Angular still require JavaScript to render their templates and create those links in the first place. Four out of every 500 people who visit a website built with Angular or Ember will encounter a completely blank page.

A solution?#section4

When dynamic web page content is rendered by a server, rendering code only has to be able to run on that one server. When it’s rendered on a client, the code now has to work with every client that could possibly visit the website. Developers are now moving away from server-rendered websites because they don’t offer the sort of rich application experience that client-rendered sites can provide. But I think there’s still a role for server rendering in the new world of client-side applications.

At the moment, requiring JavaScript is a tradeoff that developers using single-page web app frameworks have to make, but it seems to me that this is exactly the sort of problem that should be handled by a framework. We are fortunate as web developers in that we write application code for the web in one of the most universal programming languages that has ever existed. If framework developers could put in the effort (which, admittedly, seems large) to get apps running in Node just as they run in the browser, initial page rendering could be handled by the server, with all subsequent activity handled by the browser. Crucially, if a server can render links into a tags, like Ember currently does on the client, it would be possible for a user who did not receive JavaScript (for whatever reason) to navigate around the website. It might be possible to get forms working as well, by running all the validation and submission logic on the server instead of on the client. If this effort could be made at the outset by a framework maintainer, then every developer using that framework could immediately transform an app that only worked on the latest web browsers into a progressively enhanced experience compatible with virtually any web client—past, present, or future.

Progressive enhancement has been important to web developers for a while now. It recognizes that the vital part of the web experience is content, and that any additional improvement to the user experience should not undermine the universal availability of content provided by the web. Current approaches to single-page web apps tend to abandon this principle, but progressive enhancement and single-page web apps are not fundamentally incompatible.

In fact, progress is being made in this arena. To improve Ember’s compatibility with search engines, for example, an in-house team is working on implementing server-side rendering. But the solution to the problems caused by single-page web apps is not purely technical: there’s a growing problem in the way people think about the web. It has become commonplace to treat the web as just another application platform—but the web is so much more than that. The web is the universal information platform. It doesn’t matter if somebody visits a website on a $2,000 iMac, or a $50 Android tablet, or the $5 web client of a future we can’t even imagine yet—in theory, at least. In practice, it’s important to make sure that we don’t sacrifice the experiences of a small number of users just so we can slightly improve the experiences of the rest, damaging the universal nature of the web in the process.

About the Author

Ross Penman

Ross Penman is a web developer and avid technologist from Scotland. A finalist for Emerging Talent of the Year at the 2014 Net Awards, Ross is often celebrated for their work to promote young people in tech. They tweet about web development and, occasionally, Pokémon training.

21 Reader Comments

  1. For Angular, the prefered linking syntax for that login button would be a regular link like so:

    <a href=”/login”>Log In</a>

    You would only need to use ng-href if the value was dynamically rendered in javascript. And even if you did use ng-href, you could technically use both and have a fallback for non JS browsers:

    <a ng-href=”/{{pathVariable}}” href=”/login”>Log In</a>

  2. Thanks for pointing that out. 🙂 Even so, though, I’m guessing most of the links on a typical Angular application are not going to even be present in a page when it’s first loaded, since they’ll probably be displayed as part of dynamic content.

  3. Server-rendering isn’t enough because SPAs usually don’t have a dedicated page for every component. Don’t believe me? Just disable JS, visit twitter.com and try to send a tweet (send it to @Meekostuff)

    What is needed is for web-sites to be developed as a HTML-payload WebAPI. Look at mobile.twitter.com (but not on Firefox). Why didn’t someone think to progressively-enhance that?

    The site will be fully-functional without JS and pages will be just primary content (and some meta-content). Pages may as well be single-column with simple styling so that it can display on older browsers, tiny devices, everywhere. Everything about a richer UI – multi-column-layout, fancy decor, auxiliary-content – can be installed as progressive-enhancement.

    What sort of JS Framework can work with this kind of web-site?

    Framesets were the original single-page-app and – in spite of all their faults – imposed a good structure on web-sites and the use of links.

    There is the opportunity now – with 20 years of hindsight – to design a successor to framesets which features:

    – content-first + progressive-enhancement
    – light-weight, seamless frames
    – responsive layout
    – multiple-views, conditional-content, etc

    This will allow (compell even) the server representation of a site (which is what browsers without JS will render) to be basically a fully-functional, HTML-payload WebAPI.

  4. Derby.js and Meteor are both js frameworks that are working on running the same code on the client and the server.

  5. Thanks for the article Ross!

    While it’s true that Meteor also runs on the server, meteor.com along with its example apps display no content with JavaScript disabled. Learning Meteor at the moment, this worries me a bit.

  6. I agree with you. I tend to use JS to enhance web forms but not to depend on it. I understand that the approach is different now but it doesn’t have to overcome the primary web purpose: get information in any kind of device.
    I’m going to test Haxe. It’s supposed to build both server & client scripts from a single project & basecode. I want to know how it does such magic.

  7. I haven’t heard of Haxe before. It looks interesting, but I don’t think it’s likely a tool that tries to build for all platforms and devices will properly support the parts of the web that make it unique.

  8. Yeah, Meteor code can be shared across the client and server, but unless they implement initial rendering on the server, users aren’t going to see any benefit from that. It’s a start, though!

  9. Nice article. I agree it’s better to use true html links when possible, especially links to content (more so than login buttons.)

    My rule of thumb is that public search-indexable web content should be readable without JavaScript, but it’s fine to require JavaScript for app-like actions like login and any content behind login.

    Most single page apps I’ve seen are behind login and therefore not instapaper-able.

  10. This is a great read and related to something I have been working on so I thought I’d share. I’ve recently been exploring HTML 1st design over at Markup.tips. My thoughts on HTML 1st design are here:
    http://markup.tips/html-ftw

    On the Markup.tips site asynchronous components such as the main navigation are powered with React.js yet fully support .no-js because there is semantic HTML powered by MODX Revolution underneath anything JavaScript enhances.

  11. In practice, it’s important to make sure that we don’t sacrifice the experiences of a small number of users just so we can slightly improve the experiences of the rest, damaging the universal nature of the web in the process.

    While I appreciate the idealism of this statement (I genuinely do), it really isn’t this simple. Every site has limited amounts of time and resources at it’s disposal. Many sites are run by individuals or groups of individuals who want to see a return on their investment. Spending extra hours to support a small percentage of users (using archaic browsers or disabling browser functionality no less) just doesn’t make financial sense to them.

    Four out of every 500 people who visit a website built with Angular or Ember will encounter a completely blank page.

    4/500 = 0.8%. Less than the 0.9% of IE7 users (http://www.w3counter.com/trends) who will also not be able to view the Angular/Ember site anyway (because they don’t support IE8-). In fact, if you combine IE 6/7/8 users, you have a far more meaningful statistical number (to a business) than the number of users who don’t support JS.

    I’m not trying to demonize your article or the idea that the web should be universal. But the web evolved far beyond what it’s founders created it to be. We have a need for powerful web tools and services that rely on JS. There’s nothing wrong with a site trying to maximize the experience for it’s largest market shares instead of the web as a whole.

    Whether you like it or not, that’s just smart business.

  12. Ross, I’m one of the 1% you discuss who disable Javascript – using NoScript. Allowing untrusted sites to run code on your browser indiscriminately is the dumbest of ideas in my opinion.

    When I encounter a site that doesn’t work properly I then have a choice – go somewhere else, or accept the security risk of allowing the site to run scripts on my browser. Often the latter results in multiple other sites wanting to run scripts too and involves playing a game of “what is the minimum set of sites I can allow temporarily to make this sucker work”. And so very often I just browse elsewhere.

  13. This is an area, like the Flash-based websites of the past, where I’m a bit conflicted. I really like some of the new web design styles, but implementing them often leaves you with this sort of problem, where the basic elements of the site break in many cases – or you need to spend tons of effort working the proper elements into the functionality you want.

    So far, my way of dealing with these items has been to hang back until standards catch up, or the trend passes. I’m proud to say I have never let a client use even a little flash outside of non-critical video.

  14. @William Morris – what scripts are you worried about websites running? You describe it as though JavaScript can run arbitrary code on your machine, I hope you know this isn’t true? The worst a site could do with JavaScript is annoy you – in which case you can leave. There hasn’t been a security risk with JavaScript since… decades ago? JS is used to write cookies, but if you’re worried about cookies then that can be dealt with in isolation, but you’re far better off using something like a third-party script blocker in such cases – no need to cut off the nose to spite the face.

    Intentionally turning off JavaScript hinders your internet experience with no tangible benefit. It’s your prerogative but it’s a drastic and often misguided decision.

    The above does assume that you spend your time on websites that aren’t actively trying to rip you off in some way – but even then JS is only going to make that slightly easier, in the same way that CSS will.

    Catering for those that *can’t* run JavaScript, now that’s something else entirely.

  15. I see how this is an issue for a lot of teams but the guys I’m working with at the moment have budget for non JS versions and we test everything in non JS so I hope we’re accounting for browsers if all capabilities.

    I am a hobby coder too so from that perspective I’ll have to do some extra reading to make sure I’m catering for as many people as possible but from within my own abilities and resources.

    Thanks for this.

    Jack
    UI Designer
    Blockety HTML Templates

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

Nothing Fails Like Success

Our own @zeldman paints the complicated catch-22 that our free, democratized web has with our money-making capitalist roots. As creators, how do we untangle this web? #LetsFixThis