As in-person events continue to be held online amid the ongoing Covid-19 pandemic, many events are finding new ways to connect with their audiences and deliver more personal, engaging experiences. It’s no different at Antler — we used to run physical Demo Day events to exhibit our portfolio companies, and now, we need to adapt the format for a decentralised, virtual audience.
I’ve previously written about our first virtual event while explaining why we chose Gatsby over Next.js to achieve excellent performance. Now we wanted to build on top of this foundation to deliver an even better live experience.
We launched this new platform for our virtual Demo Day Rewired event in Sydney. For the first time, viewers could have their questions answered live, we actively surfaced useful information about each startup as they presented, and we made it even easier to get in touch with each startup’s founders.
But Antler is present in 12 locations, each of which runs its own Demo Day, and we wanted to enable each site to deliver the same live experience we had in Sydney in one easy-to-use and customisable platform.
Here’s how we did it.
From the start, we envisioned this new virtual event experience would augment the live viewing experience by updating the page with relevant information as the live stream progresses, without the user ever having to reload the page.
Specifically, we wanted to make it a lot easier for viewers to learn more about each startup as they presented by showing
- more information about what they do,
- background on who the founders are, and
- a link to their slide deck that the viewer can read and download.
All we needed was a way to say which startup was currently presenting.
But since we already stored all our data on a Firestore database, we could use Listeners to get realtime updates to our data effortlessly. Then, we could store which startup was currently presenting in a single Firestore document, listen to that doc’s updates, and update the page accordingly. And we don’t even have to do any particular configuration or write new code thanks to community-supported libraries like
With this setup, we could also make it much easier for viewers to contact each startup through a specialised pop-up form. This experience is a marked improvement from the previous in-person one, which asked viewers to physically divert attention from the presenters and open a specific URL on their phone. Now they could do that without even switching the tab — that’s a lot less work required.
Additionally, we partnered with Slido, which provides easy tools to add Q&A and polls for live events, allowing viewers’ questions to be answered by presenters live on air.
Adding these features enhances the level of interactivity in the live experience. It shows the viewer that we truly rethought the event format for an online virtual audience and not just a rudimentary port of the original.
Now that we’d settled on using Firestore to show the currently presenting startup in realtime, we could also use the same document to store the configuration for each event, such as the event title, time, and live stream URL.
We wanted our global teams to configure their Demo Day as they see fit, so we needed a user-friendly UI to expose this config document to them. Thankfully, we didn’t have to build an entirely new UI to facilitate this, and we avoided the additional baggage of having to update the code when we add a new setting or creating a new UI element to configure a specific field.
We were already using Firetable, our open-source project that combines a spreadsheet UI with the full power of Firestore. At Antler, it allows our team to easily manage and update our internal database and automate day-to-day tasks that involve it.
We could continue to using Firetable to expose those configuration fields directly — from text fields to toggles to dropdowns that link to other documents in the database — with minimal extra work on our part and little additional training for our team. Now we just had to decide what can be configured and write the code to enable that in our Demo Day web app.
While we initially used this setup for configuring basic settings for each event, we realised we could also use it to give our team full control over the viewing experience. Our Demo Day app has four pages:
- a registration page to collect info on the viewer,
- a pre-event page so those who just registered can preview the startups,
- the live stream page with interactivity, and
- a post-event page so viewers can rewatch individual pitches.
Instead of setting timers to switch between states, we could now allow our team to change the page displayed through toggles.
Enabling this is especially useful if, for example, the live stream was running late and they weren’t ready to switch over from the pre-event page. And since it directly updates the Firestore document, it would also trigger the Firestore listener on the front-end, so again there would be zero page refreshes required. We were even able to extend this by adding small tweaks requested by one event as toggles, so we don’t modify other events and to let future events opt-in to these tweaks.
While we were willing to accept the small performance cost of switching from the lean Algolia library to the beefier Firestore one, I wanted to continue to improve the performance of the app, especially during the first load.
As detailed in the previous article, we had minimal use of static site generation: we only used it to render the page skeleton while we waited for the Algolia query to finish. We wanted to eliminate this by including a snapshot of the config document as part of Gatsby’s static build. Then, when the Firestore Listener first loads, it will update the page data with the latest (mostly minor) updates.
To retrieve the Firestore document during Gatsby’s build process, we used
@deanc/gatsby-source-firestorer so the doc can be accessible in Gatsby’s GraphQL layer. Now I know what you’re thinking: this seems like unnecessary extra work to achieve this in Gatsby and looks a lot simpler to implement in something like… Next.js. Unfortunately, we didn’t have enough time to build and test a Next.js implementation, and the current Gatsby implementation achieved the same result for our viewers anyway.
Now that we cached our configs for the static build, we could rebuild the site at any time so that the viewer gets the latest data right as they load the page. But the question was now: when do we rebuild the site? We couldn’t do this every time the config doc was updated — this would be every time a new startup presents, or every few minutes — and each rebuild would only update a small portion of the page. Rebuilding every time would be very inefficient and cost unnecessary build minutes from Netlify.
We knew we had specific situations where a rebuild is necessary: when our team updates the social media meta tags and when they switch the current page. If the static-generated site displays another page to the one set in the config doc, it will flash to the new page when the Listener loads. This flashing is a poor and potentially confusing user experience, especially if a previously-registered user logs on to the live stream page, but has to see a flash of the registration page.
Luckily, we could use Netlify’s Build Hooks feature to trigger a new build via a Cloud Function. Then, our team could activate it right in Firetable with the single click of a button, again providing full control of the virtual event to our team.
At the end of the previous article, I wrote about how we were displaying uncompressed images uploaded directly by our founders: this meant we were loading potentially lossless images, thousands of pixels wide, for an area that was only 80px wide.
I also complained about the lack of WebP support in Safari (i.e. all iOS devices). Thankfully, the next major version, Safari 14, will support this. Unfortunately for WebP, I came across an article via Hacker News that details why WebP isn’t any better than a well-compressed JPEG.
Considering these two factors, I decided to stick with JPEG and PNG when writing a Cloud Function that generates multiple, lossy-compressed thumbnails when images are upload. (I first wrote it for displaying thumbnails on Firetable and reused it here.) These thumbnails reduced the number of bytes loaded significantly, down from multiple megabytes to just hundreds of kilobytes!
Now, most viewers don’t have to look at whitespace when scrolling down the page or when new startups appear on-screen — those bytes should be downloading the live stream anyway. Our team can now also upload any image without worrying about its size. Plus, we won’t have to ask for images to be uploaded at specific sizes, and they won’t have to resize it in an image editor — or even learn how to use one.
Thanks for reading! While I still can’t link the source code, you can have a look at our virtual Demo Day events here.
Spreadsheet interface for viewing Firestore collections, documents, and subcollections.
- Add, edit, and delete rows
- Sort and filter by row values
- Resize and rename columns
27 different column types Read more
- Basic types: Short Text, Long Text, Email, Phone, URL…
- Custom UI pickers: Date, Checkbox, Single Select, Multi Select…
- Uploaders: Image, File
- Rich Editors: JSON, Code, Rich Text (HTML)
Powerful access controls with custom user roles Read more
Supercharge your database with your own scripts.
- Action field: trigger any Cloud Function
- Derivative field: populate cell with value derived from the rest of the row’s values
- Aggregate field: populate cell with value aggregated from the row’s sub-table
Integrations with external services.
- Connect Table uses Algolia to get a snapshot of another table’s row values
- Connect Service uses any HTTP endpoint to get a cell value
Firetable makes it easy to use key Firebase products