惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
Recent Announcements
Recent Announcements
D
DataBreaches.Net
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
Netflix TechBlog - Medium
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog
B
Blog
U
Unit 42
有赞技术团队
有赞技术团队
博客园 - 聂微东
GbyAI
GbyAI
宝玉的分享
宝玉的分享
F
Full Disclosure
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MyScale Blog
MyScale Blog
Jina AI
Jina AI
Martin Fowler
Martin Fowler
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Docker
P
Proofpoint News Feed
A
About on SuperTechFans
I
InfoQ
博客园 - 【当耐特】
C
Check Point Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
Y
Y Combinator Blog
Project Zero
Project Zero
WordPress大学
WordPress大学
小众软件
小众软件
AWS News Blog
AWS News Blog
博客园 - 司徒正美
T
The Exploit Database - CXSecurity.com
L
LINUX DO - 热门话题
I
Intezer
Engineering at Meta
Engineering at Meta
C
CXSECURITY Database RSS Feed - CXSecurity.com
J
Java Code Geeks
T
Tenable Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
C
CERT Recently Published Vulnerability Notes

Maxime Heckel's Blog

On Rendering the Sky, Sunsets, and Planets - The Blog of Maxime Heckel Shades of Halftone - The Blog of Maxime Heckel Field Guide to TSL and WebGPU - The Blog of Maxime Heckel On Shaping Light: Real-Time Volumetric Lighting with Post-Processing and Raymarching for the Web - The Blog of Maxime Heckel Speaking at Figma Config 2025 - The Blog of Maxime Heckel Post-Processing Shaders as a Creative Medium - The Blog of Maxime Heckel On Crafting Painterly Shaders - The Blog of Maxime Heckel The Art of Dithering and Retro Shading for the Web - The Blog of Maxime Heckel Moebius-style post-processing and other stylized shaders - The Blog of Maxime Heckel Shining a light on Caustics with Shaders and React Three Fiber - The Blog of Maxime Heckel Real-time dreamy Cloudscapes with Volumetric Raymarching - The Blog of Maxime Heckel Painting with Math: A Gentle Study of Raymarching - The Blog of Maxime Heckel Building a magical AI-powered semantic search from scratch - The Blog of Maxime Heckel Beautiful and mind-bending effects with WebGL Render Targets - The Blog of Maxime Heckel Refraction, dispersion, and other shader light effects - The Blog of Maxime Heckel The magical world of Particles with React Three Fiber and Shaders - The Blog of Maxime Heckel The Study of Shaders with React Three Fiber - The Blog of Maxime Heckel Building a Design System from scratch - The Blog of Maxime Heckel Everything about Framer Motion layout animations - The Blog of Maxime Heckel Building a Vaporwave scene with Three.js - The Blog of Maxime Heckel Cubic Bézier: from math to motion - The Blog of Maxime Heckel First steps with GPT-3 for frontend developers - The Blog of Maxime Heckel Building the perfect GitHub CI workflow for your frontend team - The Blog of Maxime Heckel Migrating to Next.js - The Blog of Maxime Heckel Advanced animation patterns with Framer Motion - The Blog of Maxime Heckel Scrollspy demystified - The Blog of Maxime Heckel The Power of Composition with CSS Variables - The Blog of Maxime Heckel My first failed SwiftUI project - The Blog of Maxime Heckel Guide to creating animations that spark joy with Framer Motion - The Blog of Maxime Heckel Using Shortcuts and serverless to build a personal Apple Health API - The Blog of Maxime Heckel SEO mistakes I've made and how I fixed them - The Blog of Maxime Heckel Going native: SwiftUI from the perspective of a React developer - The Blog of Maxime Heckel Build your own preview deployment service - The Blog of Maxime Heckel The little guide to CI/CD for frontend developers - The Blog of Maxime Heckel Immigrating to the US - The Blog of Maxime Heckel The physics behind spring animations - The Blog of Maxime Heckel Generate screenshots of your code with a serverless function - The Blog of Maxime Heckel How to use Framer Motion with Emotion styled-components - The Blog of Maxime Heckel Data Fetching with NextJS: What I learned - The Blog of Maxime Heckel Learning in public - The Blog of Maxime Heckel Fixing the dark mode flash issue on server rendered websites - The Blog of Maxime Heckel How to fix NPM link duplicate dependencies issues - The Blog of Maxime Heckel Running scheduled cross-browser end-to-end tests on Github CI - The Blog of Maxime Heckel How I built my first custom ESLint rule - The Blog of Maxime Heckel React Lazy: a take on preloading views - The Blog of Maxime Heckel Automated UI accessibility testing with Cypress - The Blog of Maxime Heckel Building a GraphQL wrapper for the Docker API - The Blog of Maxime Heckel Switching off the lights - Adding dark mode to your React app - The Blog of Maxime Heckel Getting started with Typescript on Gatsby - The Blog of Maxime Heckel Rebuilding Redux with Hooks and Context - The Blog of Maxime Heckel Asynchronous rendering with React - The Blog of Maxime Heckel Using Flow generics to type generic React components - The Blog of Maxime Heckel How to efficiently type your styled-components with Flow - The Blog of Maxime Heckel How I got started with Kubernetes on GKE - The Blog of Maxime Heckel React sub-components Part 3: Whitelisting sub-components with flow - The Blog of Maxime Heckel React sub-components Part 2: Using the new Context API - The Blog of Maxime Heckel React sub-components - The Blog of Maxime Heckel Running Golang tests with Jest - The Blog of Maxime Heckel No title No title
Static Tweets with MDX and Next.js - The Blog of Maxime Heckel
Maxime Heckel · 2021-06-01 · via Maxime Heckel's Blog

While migrating my blog to Next.js, I took the opportunity to address the big performance pitfalls that were degrading the reader's experience in the previous version. With Core Web Vitals becoming one of the biggest factors in search ranking in 2021, I needed to get my act together and finally find workarounds to these issues before they impact my overall traffic.

One of those issues was embed tweets. I often find myself in need to quote or reference a tweet in my MDX blog posts. However, using the classic Twitter embed iframe is not the best solution for that: they are slow to load and triggers a lot of Content Layout Shift (CLS) which hurts the performance of my blog.

Thankfully, by leveraging some of Next.js' key features, a bit of hacking, and also the awesome work from Vercel's Head of DevRel Lee Robinson, we can get around this problem and have tweets in MDX based pages that do not require an iframe and load instantly 🚀 like this one:

MaximeHeckel

📨 just sent the latest issue of my newsletter! Topics for this one include - looking back at one year of learning in public⭐️ - my writing process ✍️ - what's coming up next on my blog! Curious but not yet subscribed? You can read it right here 👇 https://t.co/xQRm1wrNQw

Curious how it works? Let's take a look at the solution I managed to put together to solve this problem and some MDX/Next.js magic ✨.

Coming up with a plan

The original inspiration for this solution comes from @leerob himself: a few months ago he came up with a video titled Rebuilding the Twitter Embed Widget! which covers the following:

  • what are the issues with the classic embed tweets?

  • how to leverage the Twitter API to fetch the content of tweets

  • how to build a <Tweet /> component to display the content of a tweet with the output of the Twitter API

  • how to put these pieces together to display a predefined list of tweets in a Next.js page.

However, after watching this video, one could indeed follow this method to get a predefined list of tweets to render on a dedicated route/page in a Next.js project, but this still doesn't quite solve the problem for tweets in MDX-based pages 🤔. Thus I came up with the following plan to address this gap:

Diagram showcasing the process to extract the Static Tweets out of the MDX document and render them in a Next.js page

Diagram showcasing the process to extract the Static Tweets out of the MDX document and render them in a Next.js page

The core of this plan happens at build time when every page/article of the blog gets generated:

  1. When processing a given path, we get its corresponding MDX document content by reading a static .mdx file.

  2. Each MDX file can use/import React components. When it comes to handling tweets, I planned on using the following interface/component: <StaticTweet id="abcdef123"/> where the id prop contains the id of the tweet I want to render.

  3. Then, by using some regex magic (I'll detail the code later in this article) we can extract each StaticTweet component from the content of the MDX document, and finally get a list of tweet ids where each id represents a tweet we want to eventually render.

  4. This list of tweet ids is then returned in getStaticProps and used to fetch each tweet from the Twitter API and eventually get a map of tweet ids to tweet content (see first code snippet below). This map will help us find the content associated with each static tweet.

  5. Finally, the most "hacky" part of this implementation: rendering each tweet declared in the MDX document with the proper content (you'll see why it's "hacky" in the next part 😄).

Sample map of tweet ids to tweet content

2

'1392141438528458758': {

3

created_at: '2021-05-11T15:35:58.000Z',

5

"📨 just sent the latest issue of my newsletter!\n\nTopics for this one include\n- looking back at one year of learning in public⭐️\n- my writing process ✍️\n- what's coming up next on my blog!\n\nCurious but not yet subscribed? You can read it right here 👇\nhttps://t.co/xQRm1wrNQw",

6

id: '1392141438528458758',

13

author_id: '116762918',

15

referenced_tweets: [],

18

'https://pbs.twimg.com/profile_images/813646702553010176/rOM8J8DC_normal.jpg',

21

url: 'https://t.co/CePDMvig2q',

24

username: 'MaximeHeckel',

27

'1386013361809281024': {

29

media_keys: ['3_1386013216527077377'],

31

created_at: '2021-04-24T17:45:10.000Z',

33

"24h dans le volume d'une Fiat 500 avec trois amis et pourtant on se sent comme chez soi... à 400 km d'altitude ! Superbe performance technique et opérationelle de toutes les équipes qui nous ont entrainés et encadrés pour ce voyage 👏 https://t.co/kreeGnnLUM",

34

id: '1386013361809281024',

41

author_id: '437520768',

45

url: 'https://pbs.twimg.com/media/EzwbrVEX0AEdSDO.jpg',

47

media_key: '3_1386013216527077377',

51

referenced_tweets: [],

54

'https://pbs.twimg.com/profile_images/1377261846827270149/iUn8fDU6_normal.jpg',

57

url: 'https://t.co/6gdcdKt160',

58

name: 'Thomas Pesquet',

60

username: 'Thom_astro',

The implementation: a mix of regex, static site generation, and a hack

Now that we went through the plan, it's time to take a look at the implementation. There are 3 major pieces to implement:

  1. Using regex to find all the occurrences of StaticTweet and eventually get a list of tweet ids from the MDX document.

  2. In getStaticProps, i.e. during static site generation, use that list of tweet ids to fetch their corresponding tweets with the Twitter API and return the map of tweets to id so the Next.js page can use it as a prop.

  3. Define the StaticTweet component.

Our first step consists of getting the list of ids of tweets we want to later fetch during the "static site generation" step. For that, I took the easy path: **using regex to find each occurrence of ** StaticTweet when reading the content of my MDX file.

Most MDX + Next.js setups, including this blog, have a function dedicated to reading and parsing the content of MDX files/documents. One example of such function can be found in Vercel's own tutorial to build an MDX-based blog with Next.JS: getDocBySlug. It's in this function that we'll extract each StaticTweet and build the list of ids:

Extraction of each occurrence of StaticTweet

1

import matter from 'gray-matter';

2

import { serialize } from 'next-mdx-remote/serialize';

5

const TWEET_RE = /<StaticTweet\sid="[0-9]+"\s\/>/g;

7

const docsDirectory = join(process.cwd(), 'docs')

9

export function getDocBySlug(slug) {

10

const realSlug = slug.replace(/\.md$/, '')

11

const fullPath = join(docsDirectory, `${realSlug}.md`)

12

const fileContents = fs.readFileSync(fullPath, 'utf8')

13

const { data, content } = matter(fileContents)

19

const tweetMatch = content.match(TWEET_RE);

30

const tweetIDs = tweetMatch?.map((mdxTweet) => {

31

const id = mdxTweet.match(/[0-9]+/g)![0];

35

const mdxSource = await serialize(source)

41

tweetIDs: tweetIDs || []

Here, we execute the following tasks:

  • extract each occurrence of StaticTweet

  • extract the value of the id prop

  • return the array of ids along with the content of the article

Build a map of tweet ids to tweet content

This step will be a bit easier since it mostly relies on @leerob's code to fetch tweets that he detailed in his video. You can find his implementation on his blog's repository. My implementation is the same as his but with Typescript type definitions.

At this stage, however, we still need to do some little edits in our getStaticProps function and Next.js page:

  • Get the tweet ids out of the getDocBySlug

  • Fetch the content associated with each tweet id

  • Return the map of tweet ids to tweet content

  • Read the map of ids tweet ids to tweet content in the Next.js page code.

Fetch the list of tweets and inject the content in the page

1

import Image from 'next/image';

2

import { MDXRemote } from 'next-mdx-remote';

3

import { Heading, Text, Pre, Code } from '../components';

14

export default function Post({ mdxSource, tweets }) {

17

return <MDXRemote {...mdxSource} components={components} />;

20

export async function getStaticProps({ params }) {

21

const { mdxSource, frontMatter, slug, tweetIDs } = getDocBySlug(params.slug);

24

const tweets = tweetIDs.length > 0 ? await getTweets(tweetIDs) : {};

Define the StaticTweet component

This is where the core of this implementation resides, and also where things get a bit hacky 😬.

We can now, at build time, for a given path, get the content of all the tweets present in a corresponding MDX document. But now the main problem is: how can we render that content?

It's at this stage that I kind of hit a wall, and had to resolve to use, what I'd call, "unconventional patterns" and here are the reasons why:

  • we can't override the interface of my MDX component. MDX makes us use the same interface between the definition of the component and how it's used in the MDX documents, i.e. in our case it takes one id prop, so it can only be defined with an id prop. Thus we can't simply define an MDX component for StaticTweet and call it a day.

  • our map of tweet ids to tweet content is only available at the "page" level, and thus can't be extracted out of that scope.

One way to fix this is to define the StaticTweet component inline, i.e. inside the Next.js page, and use the map returned by getStaticProps in the definition of the component:

Definition of the StaticTweet component used in MDX documents

1

import Image from 'next/image';

2

import { MDXRemote } from 'next-mdx-remote';

3

import { Heading, Text, Pre, Code, Tweet } from '../components';

14

export default function Post({ mdxSource, tweets }) {

15

const StaticTweet = ({ id }) => {

17

return <Tweet tweet={tweets[id]} />;

Usually, I'd not define a React component this way and even less with external dependencies that are not passed as props, however in this case:

  • it's only to render static data, thus that map will never change after the static site generation

  • it's still a valid Javascript pattern: our StaticTweet component definition is inherently a Javascript function and thus has access to variables outside of its inner scope.

So, it may sound a bit weird but it's not a red flag I promise 😄.

The result

We now have everything in place to render Static Tweets in our Next.js + MDX setup so let's take a look at a couple of examples to show what this implementation is capable of.

In the MDX document powering this same blog post, I added the following StaticTweets:

1

<StaticTweet id="1397739827706183686" />

3

<StaticTweet id="1386013361809281024" />

5

<StaticTweet id="1384267021991309314" />

The first one renders a standard tweet:

The following one renders a tweet with images:

24h dans le volume d'une Fiat 500 avec trois amis et pourtant on se sent comme chez soi... à 400 km d'altitude ! Superbe performance technique et opérationelle de toutes les équipes qui nous ont entrainés et encadrés pour ce voyage 👏 https://t.co/kreeGnnLUM

24h dans le volume d'une Fiat 500 avec trois amis et pourtant on se sent comme chez soi... à 400 km d'altitude ! Superbe performance technique et opérationelle de toutes les équipes qui nous ont entrainés et encadrés pour ce voyage 👏 https://t.co/kreeGnnLUM

Finally, the last one renders a "quote tweet":

Just updated some of my projects to fix the missing headers, thank you @leeerob for sharing https://t.co/njBo8GLohm 🔒 and some of your tips! Just a note for Netlify users: you will have to add the headers either in your netlify.toml or a header file https://t.co/RN65w73I4r

Learned about https://t.co/RAxyJCKWjZ today 🔒 Here's how to take your Next.js site to an A. https://t.co/APq7nxngVw

Learned about https://t.co/RAxyJCKWjZ today 🔒

Here's how to take your Next.js site to an A. https://t.co/APq7nxngVw

And the best thing about this implementation: the resulting will remain as fast no matter how many tweets you add in your MDX document!

Pretty sweet right? ✨

Conclusion

First of all, thank you @leerob for the original inspiration for this implementation 🙌! This was yet another moment where I saw how Next.js and static site generation can shine.

I hope you all liked this little extension of Lee's static tweets tutorial. Adding support for MDX-based pages while keeping the interface clean was no easy feat as you can see but the result is definitely worth the effort and hours of tinkering put into this.

I'm still looking to improve the <Tweet /> component as I'm writing these words. There are yet a few elements that remain to be tackled in my current implementation, such as:

  • figuring out a clean/secure way to parse links, right now they just render as text

  • providing a better way to render a grid of images, as of now some images might see their aspect ratio altered

  • parsing numbers, i.e. displaying 118k instead of 118000 when it comes to likes, retweets, or replies

It's not perfect but for now, it will do! I revisited previous blog posts that referenced tweets and replaced them with this new component to guarantee the best reading experience. If you have any suggestions or ideas on how I could further improve how tweets are rendered on my blog, as always, don't hesitate to reach out! I love hearing your feedback!