Skip to content

Commit cbe2195

Browse files
techgangbossclaude
andcommitted
fix: move OG image to /og/ path — outside /api/ to avoid robots.txt cache
Twitter may have cached the old robots.txt that blocked all /api/ paths. Even though we added Allow: /api/og/, Twitter's robots.txt cache can persist for days. Moving the image endpoint to /og/preconfirm/[time] which was never blocked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d87a35b commit cbe2195

5 files changed

Lines changed: 235 additions & 2 deletions

File tree

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { ImageResponse } from "next/og"
2+
import { NextRequest } from "next/server"
3+
4+
export const runtime = "edge"
5+
6+
const ICON_DATA_URI = "data:image/png;base64,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"
7+
8+
export async function GET(
9+
request: NextRequest,
10+
{ params }: { params: Promise<{ time: string }> }
11+
) {
12+
const { time: timeParam } = await params
13+
const raw = parseFloat(timeParam || "0.4")
14+
const time = !isNaN(raw) && raw >= 0 && raw <= 999 ? raw.toFixed(1) : "0.4"
15+
16+
const [clonoidFont, soraFont] = await Promise.all([
17+
fetch(new URL("../fonts/clonoid-digits.ttf", import.meta.url)).then((r) =>
18+
r.arrayBuffer()
19+
),
20+
fetch(new URL("../fonts/sora-subset.ttf", import.meta.url)).then((r) =>
21+
r.arrayBuffer()
22+
),
23+
])
24+
25+
return new ImageResponse(
26+
<div
27+
style={{
28+
width: "100%",
29+
height: "100%",
30+
display: "flex",
31+
flexDirection: "column",
32+
alignItems: "center",
33+
justifyContent: "center",
34+
background:
35+
"linear-gradient(160deg, #020810 0%, #06122a 30%, #0e2348 55%, #091a35 80%, #040c18 100%)",
36+
position: "relative",
37+
overflow: "hidden",
38+
}}
39+
>
40+
{/* Vignette */}
41+
<div
42+
style={{
43+
position: "absolute",
44+
inset: 0,
45+
background:
46+
"radial-gradient(ellipse 85% 75% at 50% 42%, transparent 25%, rgba(0,0,0,0.5) 100%)",
47+
}}
48+
/>
49+
50+
{/* Primary glow — large, soft */}
51+
<div
52+
style={{
53+
position: "absolute",
54+
width: "900px",
55+
height: "550px",
56+
borderRadius: "50%",
57+
background:
58+
"radial-gradient(ellipse, rgba(25, 100, 220, 0.35) 0%, rgba(15, 70, 170, 0.1) 40%, transparent 65%)",
59+
top: "25%",
60+
left: "50%",
61+
transform: "translate(-50%, -50%)",
62+
}}
63+
/>
64+
65+
{/* Inner bright glow behind number */}
66+
<div
67+
style={{
68+
position: "absolute",
69+
width: "450px",
70+
height: "280px",
71+
borderRadius: "50%",
72+
background:
73+
"radial-gradient(ellipse, rgba(60, 150, 255, 0.18) 0%, rgba(40, 120, 255, 0.06) 50%, transparent 70%)",
74+
top: "36%",
75+
left: "50%",
76+
transform: "translate(-50%, -50%)",
77+
}}
78+
/>
79+
80+
{/* Horizon line */}
81+
<div
82+
style={{
83+
position: "absolute",
84+
width: "100%",
85+
height: "1px",
86+
top: "66%",
87+
background:
88+
"linear-gradient(90deg, transparent 3%, rgba(0, 150, 255, 0.08) 20%, rgba(0, 200, 255, 0.5) 50%, rgba(0, 150, 255, 0.08) 80%, transparent 97%)",
89+
}}
90+
/>
91+
92+
{/* Line glow bloom */}
93+
<div
94+
style={{
95+
position: "absolute",
96+
width: "40%",
97+
height: "50px",
98+
top: "63.5%",
99+
left: "30%",
100+
background:
101+
"radial-gradient(ellipse, rgba(0, 180, 255, 0.07) 0%, transparent 70%)",
102+
}}
103+
/>
104+
105+
{/* Decorative side accents — left */}
106+
<div
107+
style={{
108+
position: "absolute",
109+
width: "200px",
110+
height: "1px",
111+
top: "42%",
112+
left: "4%",
113+
background:
114+
"linear-gradient(90deg, transparent, rgba(60, 160, 255, 0.15), transparent)",
115+
transform: "rotate(-2deg)",
116+
}}
117+
/>
118+
119+
{/* Decorative side accents — right */}
120+
<div
121+
style={{
122+
position: "absolute",
123+
width: "200px",
124+
height: "1px",
125+
top: "44%",
126+
right: "4%",
127+
background:
128+
"linear-gradient(90deg, transparent, rgba(60, 160, 255, 0.15), transparent)",
129+
transform: "rotate(2deg)",
130+
}}
131+
/>
132+
133+
{/* Content */}
134+
<div
135+
style={{
136+
display: "flex",
137+
flexDirection: "column",
138+
alignItems: "center",
139+
position: "relative",
140+
zIndex: 1,
141+
marginTop: "-20px",
142+
}}
143+
>
144+
{/* SWAP PRECONFIRMED */}
145+
<div
146+
style={{
147+
fontFamily: "Sora",
148+
fontSize: "19px",
149+
fontWeight: 600,
150+
color: "rgba(170, 210, 255, 0.6)",
151+
letterSpacing: "0.45em",
152+
textTransform: "uppercase" as const,
153+
marginBottom: "24px",
154+
}}
155+
>
156+
Swap Preconfirmed
157+
</div>
158+
159+
{/* Number + sec */}
160+
<div
161+
style={{
162+
display: "flex",
163+
alignItems: "baseline",
164+
gap: "18px",
165+
}}
166+
>
167+
<div
168+
style={{
169+
fontFamily: "Clonoid",
170+
fontSize: "210px",
171+
color: "#ffffff",
172+
lineHeight: 0.82,
173+
letterSpacing: "-0.02em",
174+
}}
175+
>
176+
{time}
177+
</div>
178+
<div
179+
style={{
180+
fontFamily: "Sora",
181+
fontSize: "44px",
182+
fontWeight: 600,
183+
color: "rgba(170, 210, 255, 0.5)",
184+
lineHeight: 1,
185+
}}
186+
>
187+
sec
188+
</div>
189+
</div>
190+
</div>
191+
192+
{/* Bottom: logo + fastprotocol.io */}
193+
<div
194+
style={{
195+
position: "absolute",
196+
bottom: "34px",
197+
display: "flex",
198+
alignItems: "center",
199+
gap: "14px",
200+
}}
201+
>
202+
<img
203+
src={ICON_DATA_URI}
204+
width={48}
205+
height={48}
206+
style={{ width: "48px", height: "48px" }}
207+
/>
208+
<div
209+
style={{
210+
fontFamily: "Sora",
211+
fontSize: "22px",
212+
fontWeight: 600,
213+
color: "rgba(150, 200, 255, 0.4)",
214+
letterSpacing: "0.06em",
215+
}}
216+
>
217+
fastprotocol.io
218+
</div>
219+
</div>
220+
</div>,
221+
{
222+
width: 1200,
223+
height: 630,
224+
fonts: [
225+
{ name: "Clonoid", data: clonoidFont, style: "italic", weight: 700 },
226+
{ name: "Sora", data: soraFont, style: "normal", weight: 600 },
227+
],
228+
headers: {
229+
"Cache-Control": "public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800",
230+
},
231+
}
232+
)
233+
}
2.52 KB
Binary file not shown.
5.16 KB
Binary file not shown.

src/app/share/preconfirm/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export async function GET(request: NextRequest) {
1212
const title = `Preconfirmed in ${time}s — Fast Swaps`
1313
const description = `Swap preconfirmed in ${time} seconds on Fast Protocol`
1414
const origin = request.nextUrl.origin
15-
const ogImage = `${origin}/api/og/preconfirm/${time}`
15+
const ogImage = `${origin}/og/preconfirm/${time}`
1616

1717
const html = `<!DOCTYPE html>
1818
<html>

src/components/swap/SwapToast.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ export function SwapToast({ hash }: { hash: string }) {
472472
: `${window.location.origin}`
473473
// Pre-warm the OG image CDN cache before Twitter's crawler fetches it
474474
if (secs <= 4.1) {
475-
fetch(`${window.location.origin}/api/og/preconfirm/${elapsedSec}`).catch(() => {})
475+
fetch(`${window.location.origin}/og/preconfirm/${elapsedSec}`).catch(() => {})
476476
}
477477
window.open(
478478
`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(shareUrl)}`,

0 commit comments

Comments
 (0)