Closed swyxio closed 2 years ago
i learned that Steven from Vercel worked on a different twitter card impl here: https://github.com/vercel/examples/tree/main/solutions/static-tweets-tailwind
https://twitter.com/steventey/status/1536118626251587585?s=21&t=WkeX7gPbNNJvvjmbV3DJHg
Ian Muchina weighs in with his impl: https://github.com/ianmuchina/blog/tree/4e14ac7e110e799509474854e0026447f76a49a9/layouts/partials/tweet
related blogpost: https://ianmuchina.com/blog/12-tweet-embed/
his svelte impl: https://github.com/ianmuchina/tweet-component/tree/main/src/lib
I implemented static tweets for a Next.js app recently and I was able to get up-to-speed quickly (from knowing nothing about Twitter API) thanks to this write-up. So thank you very much for sharing this.
When building the tweet component, I observed a few things not mentioned in your blog post. So I thought of sharing it here:
entities
to the tweet.fields
parameter, we can access the unfurled URLs without any additional work. Using tweet ID 1535727356849135617
as an example, the first item in data.entities.urls
array is:{
"start": 45,
"end": 68,
"url": "https://t.co/L4VF9a8ukZ",
"expanded_url": "https://buff.ly/3Qc0MIq",
"display_url": "buff.ly/3Qc0MIq",
"images": ["..."],
"status": 200,
"title": "...",
"unwound_url": "https://www.lastweekinaws.com/podcast/screaming-in-the-cloud/learning-in-public-with-swyx/"
}
text
in a retweet will get truncated if it exceeds the character limit (note: this is not an issue if we only display the original tweet). To solve this, we need to get the full text from the original tweet, which is available in referenced_tweets
via expansion.I hope this information may be useful to you, or to anyone reading this comment.
thank you very much @itsuka-dev ! any chance your tweets impl is open source? in case other pple find this
I just set the visibility of my project to public. 😨 Here is the source code to my tweet component. It is messy and incomplete, so I don't think it can be used for reference. And here's a demo of the component (I only managed to implement basic features so far).
all good, it will probably help someone out there who is searching for solutions to this problem :)
category: note cover_image: https://user-images.githubusercontent.com/6764957/173245886-28bcf37d-ac5c-4e9d-9a88-58dc7e0e9e52.png
I've been unhappy with my tweet rendering strategy for a while - Twitter encourages you to use their heavy JS script to render tweets, which undoubtedly heaps all sorts of tracking on the reader, docks your lighthouse performance score by ~17 points, adds ~4 seconds to Time to Interactive, occasionally gets adblocked (so nothing renders!)
perf impact screenshots
https://pagespeed.web.dev/report?url=https%3A%2F%2Fswyxkit.netlify.app%2Fsupporting-youtube-and-twitter-embeds ![image](https://user-images.githubusercontent.com/6764957/173247953-8514aef7-ecb8-428a-810c-a520079c5531.png) https://www.webpagetest.org/result/220612_BiDcVR_5KK/1/details/#waterfall_view_step1 ![image](https://user-images.githubusercontent.com/6764957/173248017-d33f20bb-9f4d-4bb3-99d4-49769ae0235e.png)The solution, of course, is to render it yourself, hopefully on the server side.
Solution up front
You can see my Svelte REPL solution here and paste in your own
data
generated from any curl request:Use https://developer.twitter.com/apitools/api?endpoint=/2/tweets&method=get to construct your curl query; you'll also need a $BEARER_TOKEN from a twitter developers app you have to set up separately.
The problem
However, a Tweet isn't just a simple data object. Tweets can have polls, images, videos, quote tweets, likes, retweets, quote tweets, mentions, hashtags, threads/conversations, and on and on. This is a lot of product complexity to model and display correctly.
The Next.js team have put together a nextjs component with some nifty AST parsing and serverside cheerio automation: https://static-tweet.vercel.app/ but even here I found that it doesnt correctly handle video tweets and omits displaying retweets.
(use https://developer.twitter.com/apitools/api?endpoint=/2/tweets&method=get to construct your curl query
Basic display
I started out modeling the component in the Sveltejs REPL: https://svelte.dev/repl/7a576202df06467c957b8ff64dfb2e73?version=3.48.0
Part of this was just a mix of grabbing some relevant Nextjs code, but then making different design choices like taking Twitter's actual SVG icons and displaying retweets. This was brain numbing and took a couple hours but wasn't too hard.
More work can be done to add polls, images and videos but I chose to skip that for my basic implementation.
Tweet Body parsing
The text parsing became the tricky part.
Simple text like
@swyx on “learning in public” Have a listen: https://t.co/L4VF9a8ukZ https://t.co/QnVqvu8zRc
rendered on Twitter is enriched intoThis is a very basic part of the twitter experience so I wanted to model it properly.
Here is the research I did on available options:
twitter-text
library: https://github.com/twitter/twitter-text/tree/master/js but it uses core-js so is unsuitable for frontend usageThe test code I used was this:
which gets you:
I also found this small library http://blessanm86.github.io/tweet-to-html/ but it seems to use Twitter's v1 API response which is useless for modern needs.
t.co unfurling - the failed attempt
Twitter's t.co link shortening is user unfriendly because it adds tracking, latency, and makes the url opaque. (docs, docs)
To unfurl the t.co URL to something more user friendly, we could add an async process to send a ping to the t.co url, and get back the redirect header (you can use the followRedirects in the fetch API). Autolinker does not seem to support this or an async replacement function.
You can read Loige's blogpost: https://loige.co/unshorten-expand-short-urls-with-node-js/ for the basic intuition and use his
tall
library:There is also a
url-unshort
library to do this with retries and caching:I ended up going with
tall
and postprocessing autolinker:which correctly unshortened the URLs
However I found that I needed to run this unshortening in the browser and the
tall
library requires Node.js'http
module, so back to square one for me.t.co unfurling - the simple way
A discovery I had in testing these unfurls was the sneaky way that Twitter makes it hard for you to unwrap the url. if you
fetch('https://t.co/L4VF9a8ukZ')
, you get back<head><noscript><META http-equiv="refresh" content="0;URL=https://buff.ly/3Qc0MIq"></noscript><title>https://buff.ly/3Qc0MIq</title></head><script>window.opener = null; location.replace("https:\/\/buff.ly\/3Qc0MIq")</script>
which basically forces an in-place reload rather than using the proper HTTP redirect headers. annoying!However, assuming Twitter's redirect response is stable, you can exploit this.
et voila...
Rendering images
The API forces you to perform a lookup, which isn't the hardest thing in the world to do. (The CSS is harder)
aside: Nextjs gets the aspect ratio presentation wrong
![image](https://user-images.githubusercontent.com/6764957/173249832-d1b40143-fe0c-4a6b-a9fb-aad28a490066.png)Then it's just a matter of getting some test cases:
I wasn't super confident in my css grid ability so I blended some css grid with JS to represent the different layouts (particularly the 3-image layout):
Rendering Video
The next thing to do is video. Nextjs doesnt handle it so we'll have to figure this out.
For a single embedded video, Twitter provides a bunch of different bitrates:
I considered offering a custom player, but felt that wasn't worth it. So I just offer a basic video control:
This was the best blogpost I found on the topic: https://blog.addpipe.com/10-advanced-features-in-html5-video-player/
Polls
Here's a generic search for all polls: https://mobile.twitter.com/search?q=card_name%3Apoll2choice_text_only%20OR%20card_name%3Apoll3choice_text_only%20OR%20card_name%3Apoll4choice_text_only%20OR%20card_name%3Apoll2choice_image%20OR%20card_name%3Apoll3choice_image%20OR%20card_name%3Apoll4choice_image%20&src=typed_query&f=top
Polls can have:
It wasn't too bad:
Other Twitter features
There are Twitter Lists, Twitter communities, Twitter Spaces, and others, that I don't handle well, but at least it doesnt look actively horrible:
To handle this well I would have to write an "unfurl" module to unfurl quote tweets and these other features. Can't be bothered right now.