Google has been hosting the Chrome Dev Summit (CDS) conference around November every year since 2013. It's a place to engage with Chrome engineers and web folks—check out the recordings here.
And every conference needs its own website. In CDS' case, it's a static site containing some information and schedule data. Reservations were handled by another system (and liberal application of Google Forms). In 2018, we built on previous work and rewrote the site, with these requirements:
- Fast (obviously)
- Progressive (supporting old browsers)
- Indexable (talks are SEO'ed)
So let's get into how we built it. 👍
The Basics
The CDS site runs on a simple Node.JS server, rendering plain HTML for pages and their subpages. Every bit of content, whether it be a FAQ or information about a speaker, is something a browser can load with a real URL. We're not using any specific framework—we just wanted plain HTML.
We decided early on that we wanted to support a no-JavaScript experience. This was important to us to include the long tail of browsers, including low-end devices like those running KaiOS (which, under the hood, runs Firefox 48)—see the high-quality 'screenshot' below! 😮📸
We still want JS, of course: while the site is primarily for serving information, there's a couple of places where it's nice to have.
Progressive Enhancement
Our JS is shipped using <script type="module" src="code.js">
. Using the "module" type is important, as it will only target browsers that support ES6 modules: modern browsers from about early 2018 onwards.
This is a very intentional choice:
Most users accessing CDS are on modern browsers (CDS is targeted towards a tech audience, which tends to have high update rates)
This lets us use ES6 features like
await
andPromise
without including extra code (read more about high water marks)We only have two targets: modern, evergreen browsers that support modules, or no-JS browsers. By reducing the surface area—there's no middle ground—we can more aggressively test both extremes.
Enhanced Experiences
So, if you load up the CDS site now (I enjoy this empty page) and click a link, you'll see that changing between pages gives a transition effect in-line with the conference theme. Without JavaScript, the link is just that: it'll load entirely new HTML.
The enhanced experience works a bit like this:
a. Kick off loading the new page via window.fetch
, holding the Promise
b. Fade out the current page with a CSS transition, and desaturate the masthead image (filter: grayscale(1)
)
c. await
the fetch Promise
, then extract the content from the <main>
tag of the whole other page—we're not requesting partials here, so there's a tiny overhead in re-requesting the <head>
etc
This works literally by inserting the full HTML of the other page into a dummy element:
// just dump the HTML into a tag so we can look for main
const node = document.createElement('div');
node.innerHTML = raw;
const recievedMain = node.querySelector('main'); // main from incoming DOM
realMain.innerHTML = recievedMain.innerHTML;
d. Call history.pushState
with the new URL, and fade in the new content with CSS
And if any of the process above fails, we fall back to setting the browser's location to the new URL via (effectively) a giant try/catch
block. This is just one example, but all of our JS works progressively using this same approach—only shipped with type="module"
, and always wrapped in safety blocks.
Building JavaScript
Our JavaScript lives in several files with a single entrypoint (as all pages in the site are effectively the same, wiki-style content). As mentioned, we use ES modules, and the entrypoint is at src/bundle.js
—in development, this goes on to statically include further files (including some from our node_modules
folder), just so that our code has a good layout.
⚠️ We include ES modules from node_modules
, but you can't just naïvely import older-style require()/module.exports
code without an extra rollup plugin, and you will need a build step even for development.
To build, we simply use Rollup to concatenate all of our source into a single file. So that even though our code requires ES modules, we don't use importing or exporting in production. It's just a high water mark for features, and our client browsers then only have to fetch one single file to make the site 'go'.
Indexability
Load one of our sessions in an incognito window (important!). You'll notice there's nothing 'behind' the session popup: this is just a session itself.
This page is actually an AMP, which works really well: it's a "leaf node", which AMP is designed for (think article, item in a store, session, speaker etc).
If you now reload this page, or navigate to it from the schedule itself, you'll see that the session appears as a popup. This is another enhancement, and there's a few parts to this:
Once you load the page, we install a Service Worker, which lets the site load offline by controlling all network traffic (and note that Service Workers are not supported by Googlebot)
For any URL like
/devsummit/schedule/session-name
, we just serve the regular schedule pageThe JavaScript on the schedule page identifies the new URL route, and shows the session popup
In this way, our sessions can be displayed in an enhanced fashion, as part of the SPA with JavaScript. And if this flow isn't supported, we always have the fallback option: raw (AMP) HTML, something literally every search engine and browser understands.
CSS and design
We use Less CSS for our CSS. Rather than doing any ongoing build process, in development, we include the source and its runtime parser directly:
<!-- dev less -->
<link rel="stylesheet/less" type="text/css" href="./static/styles/cds.less" />
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.7.1/less.min.js"></script>
And in prod, we ship the compiled CSS file.
Challenges
It's all well and good to ship a no-JS solution for older browsers, or for search engines, but there's some parts which are traditionally complex.
Interactive Sidebar
If you load CDS on a small device (e.g., a KaiOS device... or just shrink your window), you might notice one component that is quite interactive and required for navigation- the sidebar.
We can actually do this entirely without scripting. Fundamentally, we can leverage CSS' adjacency selector.
How does this work? The button to open the sidebar (known as the "hamburger menu", ☰) is actually a <label>
pointing to a transparent (but not hidden) <input type="checkbox">
. When checked, we can add a CSS rule bringing an adjacent element into view:
input#sidebar + .sidebar {
transform: translate(100%); /* offscreen if not checked */
opacity: 0;
}
input#sidebar:checked + .sidebar {
transform: translate(0); /* visible if checked */
opacity: 1;
}
Here's a longer demo of how this works:
We actually do a bit more work to ensure that the sidebar is not just offscreen, but also properly removed from the DOM, when it's closed. Notably, we animate the visibility
property so that it is set to visibility: hidden
when the animation is gone.
What's the takeaway here? Every browser understands forms, so we can make them work for us. 👍
Regrets
While most of CDS' content is statically generated (e.g., the FAQ or other pages), the session and speaker popups are is somewhat manually generated with raw calls to the DOM- think lists of querySelector('#foo').textContent = '...';
.
We do this for two reasons.
We don't use a templating language- if we started again, we would use
lit-html
as an easy way to stamp out complex HTML subtreesThere was no obvious approach to isomorphic templates.
lit-html
, for instance, has (as of writing) no support for running in a Node.JS backend- where there's no native DOM.
Thanks
I hope you've enjoyed this short writeup. What have I missed?
Google builds its sites for different reasons and with different stacks depending on team—I'm also involved in the I/O 2019 website, but there we're using Preact.
I've not covered the Service Worker for the site, but we do have one, and the site works great offline. Notably, we don't cache individual session HTML, because the SPA-style popup can take over in this case.
The site is hosted using the Koa server and build instructions are specified via Gulp, but we're not tied to either.
You can check out the code on GitHub—although note that we're in the "dev-summit-18" branch.
Thanks for reading! 🎉 If you have more questions, hit me up on Twitter.