Is it possible to combine Vercel OG image generation with react-tweet from Vercel in a NextJS app?
Answered
Grand Basset Griffon Vendéen posted this in #help-forum
Grand Basset Griffon VendéenOP
Basically, can I combine the static-tweet from vercel and generate an image of it's output?
Answered by joulev
ImageResponse uses [satori](https://github.com/vercel/satori) which imposes a lot of limitations on the jsx it takes in. <Tweet /> is not compatible with these limitations.
You have to use [getTweet](https://react-tweet.vercel.app/api-reference#gettweet) and [enrichTweet](https://react-tweet.vercel.app/api-reference#enrichtweet) to build your own <Tweet /> component that can satisfy the limitations on css and html markup that satori imposes.
You have to use [getTweet](https://react-tweet.vercel.app/api-reference#gettweet) and [enrichTweet](https://react-tweet.vercel.app/api-reference#enrichtweet) to build your own <Tweet /> component that can satisfy the limitations on css and html markup that satori imposes.
35 Replies
Grand Basset Griffon VendéenOP
I'm trying to create a NextJs app that has an api endpoint where I can give a tweet id, and an image of that tweet is returned.
static-tweet works wel, but I'm unable to get it to work with image generation.
Is this me or just a technical limitation?
static-tweet works wel, but I'm unable to get it to work with image generation.
Is this me or just a technical limitation?
import { ImageResponse } from 'next/og';
import { Tweet } from 'react-tweet'
// App router includes @vercel/og.
// No need to install it.
export async function GET() {
//let tweet = await fetchTweet('1825888113224266042');
//console.log(tweet);
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flexWrap: 'nowrap',
backgroundColor: 'white',
backgroundImage: 'radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)',
backgroundSize: '100px 100px',
}}
>
<Tweet id="1825888113224266042" />
</div>
),
{
width: 1200,
height: 630,
},
);
} GET /api/generate 500 in 14ms
⨯ Error: failed to pipe response
[cause]: TypeError: Cannot destructure property 'children' of 'p' as it is undefined.@Grand Basset Griffon Vendéen js
import { ImageResponse } from 'next/og';
import { Tweet } from 'react-tweet'
// App router includes @vercel/og.
// No need to install it.
export async function GET() {
//let tweet = await fetchTweet('1825888113224266042');
//console.log(tweet);
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flexWrap: 'nowrap',
backgroundColor: 'white',
backgroundImage: 'radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)',
backgroundSize: '100px 100px',
}}
>
<Tweet id="1825888113224266042" />
</div>
),
{
width: 1200,
height: 630,
},
);
}
txt
GET /api/generate 500 in 14ms
⨯ Error: failed to pipe response
[cause]: TypeError: Cannot destructure property 'children' of 'p' as it is undefined.
ImageResponse uses [satori](https://github.com/vercel/satori) which imposes a lot of limitations on the jsx it takes in. <Tweet /> is not compatible with these limitations.
You have to use [getTweet](https://react-tweet.vercel.app/api-reference#gettweet) and [enrichTweet](https://react-tweet.vercel.app/api-reference#enrichtweet) to build your own <Tweet /> component that can satisfy the limitations on css and html markup that satori imposes.
You have to use [getTweet](https://react-tweet.vercel.app/api-reference#gettweet) and [enrichTweet](https://react-tweet.vercel.app/api-reference#enrichtweet) to build your own <Tweet /> component that can satisfy the limitations on css and html markup that satori imposes.
Answer
Grand Basset Griffon VendéenOP
okay, I guess that's what I will do. Thank you!
It seems like satori is only for jsx?
@Grand Basset Griffon Vendéen It seems like satori is only for jsx?
Yeah… well aren’t you already using jsx?
Grand Basset Griffon VendéenOP
Yes, but I'm pretty new to Next and jsx. would've made it easier for me if it could just do basic html with css
but it seems like it does support tailwind, I'll look into it
Yeah must be either tailwind or inline styles
Grand Basset Griffon VendéenOP
thanks man
you're welcome, good luck
Grand Basset Griffon VendéenOP
@joulev can I tag for this?
I have no clue why fontweight and fontStyle are not working..
I have no clue why fontweight and fontStyle are not working..
I want it to be bold, but that doesn't seem to have any effect, so I tried italic and nothing works
changing the color, that does work.
I removed all css files, and in the layou, this is what there is left
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}this is where the font is imported I guess?
@Grand Basset Griffon Vendéen I removed all css files, and in the layou, this is what there is left
js
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
the ImageResponse system is separate from the router so this root layout doesn't do anything. to use a custom font you need to download the font and use the arraybuffer as an argument to ImageResponse.
example of a local font: https://vercel.com/guides/using-custom-font
example of a remote font:
example of a local font: https://vercel.com/guides/using-custom-font
example of a remote font:
const fontResponse = await fetch(".../font.ttf");
const fontData = await fontResponse.arrayBuffer();
return new ImageResponse(
<div>Hello world</div>,
{
fonts: [
{
name: 'Font name',
data: fontData,
style: 'normal',
},
],
},
);but aside from that,
fontWeight and fontStyle should work normally. !important is not needed there so should be removedGrand Basset Griffon VendéenOP
okay so even if I don't download anything, it should just work right?
I only added important because it wasn't working
@Grand Basset Griffon Vendéen okay so even if I don't download anything, it should just work right?
yes it should work. assuming the font in use supports bold and italic
[see example](https://og-playground.vercel.app/?share=XVDBSsQwEP2VYcBbYCt4kFC9Cd4VvPSSNNMmmk3CNmstpf9uppWAe8rMezPvvcyKfTSEElvjvrsAMOXF09O6cg1g3JS8WiR0OHj66VAcuFb913iJ12CYmq3LVLnZmWwZvm-au4pacqPNN_C2deGZizYdU__dAYYY8kfd1NGbKniQb7zAnMvKu76yrAywawO8kvcR5ngp62x2SoVoT-XH5UWBMWUXw4RyxT08ysemEXhERvnAjSF9HVEOyk8kkM7x070viS-X570rOhzo5azJ_M1tArPSZcRygN0ft18)
[see example](https://og-playground.vercel.app/?share=XVDBSsQwEP2VYcBbYCt4kFC9Cd4VvPSSNNMmmk3CNmstpf9uppWAe8rMezPvvcyKfTSEElvjvrsAMOXF09O6cg1g3JS8WiR0OHj66VAcuFb913iJ12CYmq3LVLnZmWwZvm-au4pacqPNN_C2deGZizYdU__dAYYY8kfd1NGbKniQb7zAnMvKu76yrAywawO8kvcR5ngp62x2SoVoT-XH5UWBMWUXw4RyxT08ysemEXhERvnAjSF9HVEOyk8kkM7x070viS-X570rOhzo5azJ_M1tArPSZcRygN0ft18)
Grand Basset Griffon VendéenOP
is this specific to ttf? doesn't seem to work for my woff2 files
⨯ unhandledRejection: TypeError: Failed to parse URL from /_next/static/media/Chirp-Bold.0f899861.woff2
at node:internal/deps/undici/undici:13178:13 {
[cause]: TypeError: Invalid URL
at new URL (node:internal/url:797:36)
at new Request (node:internal/deps/undici/undici:9269:25)
at fetch (node:internal/deps/undici/undici:9998:25)
at fetch (node:internal/deps/undici/undici:13176:10)
at fetch (node:internal/bootstrap/web/exposed-window-or-worker:72:12)
route.runtime.dev.js:6:54620 {
code: 'ERR_INVALID_URL',
input: '/_next/static/media/Chirp-Bold.0f899861.woff2'
}and I have the aseets in my root
@Grand Basset Griffon Vendéen txt
⨯ unhandledRejection: TypeError: Failed to parse URL from /_next/static/media/Chirp-Bold.0f899861.woff2
at node:internal/deps/undici/undici:13178:13 {
[cause]: TypeError: Invalid URL
at new URL (node:internal/url:797:36)
at new Request (node:internal/deps/undici/undici:9269:25)
at fetch (node:internal/deps/undici/undici:9998:25)
at fetch (node:internal/deps/undici/undici:13176:10)
at fetch (node:internal/bootstrap/web/exposed-window-or-worker:72:12)
route.runtime.dev.js:6:54620 {
code: 'ERR_INVALID_URL',
input: '/_next/static/media/Chirp-Bold.0f899861.woff2'
}
do you have the code set up with this structure? (with
import.meta.url and all) also try export const runtime = 'edge'Grand Basset Griffon VendéenOP
yes
export async function GET() {
const fontData = fetch(
new URL('../../../../assets/Chirp-Bold.woff2', import.meta.url),
).then((res) => res.arrayBuffer());that's weird, does it work with the edge runtime? also you are missing the
await keywordGrand Basset Griffon VendéenOP
It does seem to be limited to TTF
I've got it to work with ttf
but not with woff2
@Grand Basset Griffon Vendéen but not with woff2
Oh yeah indeed it does not work with woff2 yet
Grand Basset Griffon VendéenOP
yep, I've just read that too. all by all it's working pretty good tho