Skip to content

Commit e2017fd

Browse files
feat: improve tweet embedding using react-tweet
1 parent 172bd84 commit e2017fd

File tree

7 files changed

+165
-87
lines changed

7 files changed

+165
-87
lines changed

components/NotionPage.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import { type PageBlock } from 'notion-types'
77
import { formatDate, getBlockTitle, getPageProperty } from 'notion-utils'
88
import * as React from 'react'
99
import BodyClassName from 'react-body-classname'
10-
import { type NotionComponents, NotionRenderer } from 'react-notion-x'
11-
import TweetEmbed from 'react-tweet-embed'
10+
import {
11+
type NotionComponents,
12+
NotionRenderer,
13+
useNotionContext
14+
} from 'react-notion-x'
15+
import { EmbeddedTweet, TweetNotFound, TweetSkeleton } from 'react-tweet'
1216
import { useSearchParam } from 'react-use'
1317

1418
import type * as types from '@/lib/types'
@@ -97,7 +101,14 @@ const Modal = dynamic(
97101
)
98102

99103
function Tweet({ id }: { id: string }) {
100-
return <TweetEmbed tweetId={id} />
104+
const { recordMap } = useNotionContext()
105+
const tweet = (recordMap as types.ExtendedTweetRecordMap)?.tweets?.[id]
106+
107+
return (
108+
<React.Suspense fallback={<TweetSkeleton />}>
109+
{tweet ? <EmbeddedTweet tweet={tweet} /> : <TweetNotFound />}
110+
</React.Suspense>
111+
)
101112
}
102113

103114
const propertyLastEditedTimeValue = (

lib/get-tweets.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { type ExtendedRecordMap } from 'notion-types'
2+
import { getPageTweetIds } from 'notion-utils'
3+
import pMap from 'p-map'
4+
import pMemoize from 'p-memoize'
5+
import { getTweet as getTweetData } from 'react-tweet/api'
6+
7+
import type { ExtendedTweetRecordMap } from './types'
8+
import { db } from './db'
9+
10+
export async function getTweetsMap(
11+
recordMap: ExtendedRecordMap
12+
): Promise<void> {
13+
const tweetIds = getPageTweetIds(recordMap)
14+
15+
const tweetsMap = Object.fromEntries(
16+
await pMap(
17+
tweetIds,
18+
async (tweetId: string) => {
19+
return [tweetId, await getTweet(tweetId)]
20+
},
21+
{
22+
concurrency: 8
23+
}
24+
)
25+
)
26+
27+
;(recordMap as ExtendedTweetRecordMap).tweets = tweetsMap
28+
}
29+
30+
async function getTweetImpl(tweetId: string): Promise<any> {
31+
if (!tweetId) return null
32+
33+
const cacheKey = `tweet:${tweetId}`
34+
35+
try {
36+
try {
37+
const cachedTweet = await db.get(cacheKey)
38+
if (cachedTweet) {
39+
return cachedTweet
40+
}
41+
} catch (err) {
42+
// ignore redis errors
43+
console.warn(`redis error get "${cacheKey}"`, err.message)
44+
}
45+
46+
const tweetData = await getTweetData(tweetId)
47+
48+
try {
49+
await db.set(cacheKey, tweetData)
50+
} catch (err) {
51+
// ignore redis errors
52+
console.warn(`redis error set "${cacheKey}"`, err.message)
53+
}
54+
55+
return tweetData
56+
} catch (err: any) {
57+
console.warn('failed to get tweet', tweetId, err.message)
58+
return null
59+
}
60+
}
61+
62+
export const getTweet = pMemoize(getTweetImpl)

lib/notion.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
navigationLinks,
1313
navigationStyle
1414
} from './config'
15+
import { getTweetsMap } from './get-tweets'
1516
import { notion } from './notion-api'
1617
import { getPreviewImageMap } from './preview-images'
1718

@@ -64,6 +65,8 @@ export async function getPage(pageId: string): Promise<ExtendedRecordMap> {
6465
;(recordMap as any).preview_images = previewImageMap
6566
}
6667

68+
await getTweetsMap(recordMap)
69+
6770
return recordMap
6871
}
6972

lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export interface PageProps {
1818
error?: PageError
1919
}
2020

21+
export interface ExtendedTweetRecordMap extends ExtendedRecordMap {
22+
tweets: Record<string, any>
23+
}
24+
2125
export interface Params extends ParsedUrlQuery {
2226
pageId: string
2327
}

next.config.js

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,12 @@ export default withBundleAnalyzer({
1010
staticPageGenerationTimeout: 300,
1111
images: {
1212
remotePatterns: [
13-
{
14-
protocol: 'https',
15-
hostname: 'www.notion.so',
16-
pathname: '**'
17-
},
18-
{
19-
protocol: 'https',
20-
hostname: 'notion.so',
21-
pathname: '**'
22-
},
23-
{
24-
protocol: 'https',
25-
hostname: 'images.unsplash.com',
26-
pathname: '**'
27-
},
28-
{
29-
protocol: 'https',
30-
hostname: 'pbs.twimg.com',
31-
pathname: '**'
32-
},
33-
{
34-
protocol: 'https',
35-
hostname: 'abs.twimg.com',
36-
pathname: '**'
37-
},
38-
{
39-
protocol: 'https',
40-
hostname: 's3.us-west-2.amazonaws.com',
41-
pathname: '**'
42-
},
43-
{
44-
protocol: 'https',
45-
hostname: 'transitivebullsh.it',
46-
pathname: '**'
47-
}
13+
{ protocol: 'https', hostname: 'www.notion.so' },
14+
{ protocol: 'https', hostname: 'notion.so' },
15+
{ protocol: 'https', hostname: 'images.unsplash.com' },
16+
{ protocol: 'https', hostname: 'abs.twimg.com' },
17+
{ protocol: 'https', hostname: 'pbs.twimg.com' },
18+
{ protocol: 'https', hostname: 's3.us-west-2.amazonaws.com' }
4819
],
4920
formats: ['image/avif', 'image/webp'],
5021
dangerouslyAllowSVG: true,
@@ -62,5 +33,8 @@ export default withBundleAnalyzer({
6233
'node_modules/react-dom'
6334
)
6435
return config
65-
}
36+
},
37+
38+
// See https://react-tweet.vercel.app/next#troubleshooting
39+
transpilePackages: ['react-tweet']
6640
})

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,18 @@
4040
"ky": "^1.7.2",
4141
"lqip-modern": "^2.2.1",
4242
"next": "^15.0.3",
43-
"notion-client": "^7.1.1",
44-
"notion-types": "^7.1.1",
45-
"notion-utils": "^7.1.1",
43+
"notion-client": "^7.1.3",
44+
"notion-types": "^7.1.3",
45+
"notion-utils": "^7.1.3",
4646
"p-map": "^7.0.2",
4747
"p-memoize": "^7.1.1",
4848
"posthog-js": "^1.181.0",
4949
"prismjs": "^1.29.0",
5050
"react": "^18.2.0",
5151
"react-body-classname": "^1.3.1",
5252
"react-dom": "^18.2.0",
53-
"react-notion-x": "^7.2.1",
54-
"react-tweet-embed": "^2.0.0",
53+
"react-notion-x": "^7.2.3",
54+
"react-tweet": "^3.2.1",
5555
"react-use": "^17.4.2",
5656
"rss": "^1.2.2"
5757
},

0 commit comments

Comments
 (0)