The Chrome Dev Summit site: case study
Tags: javascript, webdev
Published: 2019-03-12 05:21:11

---
title: The Chrome Dev Summit site: case study
published: true
description: Some reading on the CDS 2018 site—a simple content site for Chrome's annual conference
cover_image: https://thepracticaldev.s3.amazonaws.com/i/467qad37csiika2pe09f.png
tags: javascript, webdev
---

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](https://bit.ly/CDS18-AllSessions).

And every conference needs [its own website](https://developer.chrome.com/devsummit). 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! 😮📸

![CDS on a KaiOS device](https://thepracticaldev.s3.amazonaws.com/i/qy5uaamg8581qazerzjm.jpeg)

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` and `Promise` without including extra code (read more about [high water marks](https://medium.com/dev-channel/es6-modules-in-chrome-canary-m60-ba588dfb8ab7))

* 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](https://developer.chrome.com/devsummit/empty)) 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.

![transition between pages](https://thepracticaldev.s3.amazonaws.com/i/o4zl5vozg6p4wq340r6e.gif)

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:

```js
// 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](https://github.com/rollup/rollup-plugin-commonjs), and you will need a build step _even_ for development.

To build, we simply use [Rollup](https://rollupjs.org) 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](https://developer.chrome.com/devsummit/schedule/smooth-moves) 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](https://www.ampproject.org/), 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](https://developer.chrome.com/devsummit/schedule) itself, you'll see that the session appears as a popup. This is another enhancement, and there's a few parts to this:

1. 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](https://www.youtube.com/watch?v=YP2tMBhyU0I&list=PLNYkxOF6rcIDjlCx1PcphPpmf43aKOAdF&index=15&t=0s))

2. For any URL like `/devsummit/schedule/session-name`, we just serve the regular schedule page

3. The 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](http://lesscss.org/) for our CSS. Rather than doing any ongoing build process, in development, we include the source and its runtime parser directly:

```html
<!-- 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](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator).

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:

```css
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:

{% codepen https://codepen.io/samthor/pen/daJmdx %}

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.

1. 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 subtrees

2. There 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](https://preactjs.com/).

* 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](https://www.npmjs.com/package/koa) and build instructions are specified via Gulp, but we're not tied to either.

* You can check out the code [on GitHub](https://github.com/GoogleChrome/devsummit/tree/dev-summit-18)—although note that we're in the "dev-summit-18" branch.

Thanks for reading! 🎉 If you have more questions, hit me up [on Twitter](https://twitter.com/samthor).