Here's something fun I've been fiddling with for over a year – fast privacy-focused tweet embeds. Because where's the fun in using a plugin with Twitter's native embed widget like a normal person?
And the default embed has ... problems. As I wrote in Build privacy-focused blazing fast tweet embeds, the default embed:
- loads 1.2MB of JavaScript
- makes 20+ HTTP requests
- renders 100+ DOM nodes
Exact numbers may have changed but I'm guessing in the wrong direction 🙃
My home-grown edition is tiny by comparison:
I generally find it odd how obsessed with rendering performance people are for regular webapps
— Swizec Teller (@Swizec) October 24, 2022
Yes that’s what you notice on localhost … but for users, data access and tracking scripts are by far the slow part
- 9kB of JavaScript
- 2 HTTP requests
- 35 DOM nodes
Why a web component
The blazing fast tweet embeds from a year ago worked fine. Why the need for a web component?
Last year's version relied purely on build time rendering (SSG), which has several advantages:
- no client-side JavaScript
- super fast for the reader
- no user-facing delay for Twitter's API
But there's a big problem: swizec.com contains 867 tweets. More when this article lands. Twitter's free tier API limit gives you 400 tweet lookups per 15 minute period.
That means at every deploy more than half the tweets would fail to fetch and would render as a plain link. Looked terrible and there was no telling which would fail.
I tried solving this with deferred static generation (DSG) for old articles, but that doesn't impact when the data loads. Gatsby tries to gather all the data it needs at build time and defer only the rendering of each page.
Solution: re-fetch data in the browser. Live.
Web component + SSG is magic
<twitter-lite url="https://twitter.com/Swizec/status/1584608060483387392">
<blockquote>
I generally find it odd how obsessed with rendering performance people are
for regular webapps
</blockquote>
</twitter-lite>
The <twitter-lite>
web component starts by displaying any pre-rendered HTML as a preview, re-fetches tweet data in the background, and replaces the preview with a full render. It can work on its own or supported by server side rendering of any kind.
With a full pre-render, the component looks like this:
Not sure what causes the content flash yet, but notice how the stats update? That's the data re-fetch.
When server fails to fetch full data, it can fallback on the Twitter's oEmbed API to render a simple preview:
Here the in-browser fetch gives <twitter-lite>
the full info it needs to render a tweet, but even clients without JavaScript can read the main content. Somewhere I CSS'd wrong and there's a double border ... 🙃
With zero server rendering, the component handles everything client-side:
How the <twitter-lite>
web component works
The full machinery comes in 3 parts:
- A server-side plugin that converts twitter links into previews wrapped with
<twitter-lite>
- A client-side web component
- An
/api
route that proxies Twitter requests
You can read more about the server-side plugin in Build privacy-focused blazing fast tweet embeds. One day I'll proper open source that.
Client-side web component <twitter-lite>
Web components let you build custom HTML elements with extra functionality. That's what I wanted to try with this project :)
You can see the full code on GitHub. The tweet-builder.ts file is almost the same as the server-side plugin that renders previews.
Right now you can npm install twitter-lite-embed
, but there are no docs yet. Coming soon.
The web component itself is a TypeScript class:
export class TwitterLiteEmbed extends HTMLElement {
shadowRoot!: ShadowRoot;
// holds tweet data from API
private tweet: Tweet | null = null;
// referenc to content node
private contentRef!: HTMLDivElement;
// used as a lock for fetching
private fetchingStatus: "canStart" | "fetching" | "fetched" | "error" =
"canStart";
constructor() {
super();
this.setupDom();
}
static get observedAttributes(): string[] {
return ["url"];
}
get url(): string {
return this.getAttribute("url") || "";
}
async attributeChangedCallback(
name: string,
oldVal: unknown,
newVal: unknown
) {
// call hydrateTweet if URL changes
}
private async hydrateTweet() {
// fetchTweet then renderTweet
}
private async fetchTweet() {
// fetches tweet and saves data in this.tweet
// avoids double fetching
}
private setupDom() {
// copies existing children into a new shadow dom
// saves reference to a content div
}
private renderTweet() {
// replace content HTML with rendered tweet
}
}
This class defines a new HTMLElement
with extra functionality for fetching and rendering tweets when the url=""
attribute changes.
Declaring the component in a global elements registry makes it work in the browser:
customElements.define("twitter-lite", TwitterLiteEmbed);
Now any DOM node coming from a <twitter-lite>
HTML turns into a TwitterLiteEmbed
object.
/api
route to proxy Twitter requests
Calling Twitter's API from the browser would defeat the privacy angle and leak your API keys to the public. Not great.
To get around that issue, <twitter-lite>
assumes your site runs a light endpoint at /api/fetch-tweet
. Easy to do with both Gatsby and NextJS by plopping a bit of code in your /api/
directory. It then runs as a serverless function.
You can see an example implementation, here.
Works in 2 parts:
- boilerplate for a serverless function
- API call to fetch the tweet using the twitter-lite library
I haven't tested with NextJS, but on Gatsby deployed to Gatsby Cloud, you can plop that whole file in /api/fetch-tweet
and it Just Works 🤘
Why not SSR?
Yes, server side rendering (SSR) would be another approach to solving my problem. Instead of pre-generating a static site for the whole blog, you could wait for all the machinery to run on every page request.
But that defeats the whole purpose of a static site. I don't want readers to run all that machinery on every pageview.
Cheers,
~Swizec
Continue reading about Over-engineering tweet embeds with web components for fun and privacy
Semantically similar articles hand-picked by GPT-4
- Build privacy-focused blazing fast tweet embeds – CodeWithSwiz 30
- Twitter embeds without JavaScript, pt1 – #CodeWithSwiz 29
- CodeWithSwiz: Privacy-focused embeds for YouTube, Twitter, et al
- People like me are why you shouldn't run a hosting company
- The surprising performance boost from changing gif embeds
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️