diff --git a/src/components/common/LinkPreviewHead.tsx b/src/components/common/LinkPreviewHead.tsx
index b8aaeb83f3..5a1bd6f3fd 100644
--- a/src/components/common/LinkPreviewHead.tsx
+++ b/src/components/common/LinkPreviewHead.tsx
@@ -4,8 +4,16 @@ type Props = {
path: string
}
+const getTimestamp = () => {
+ const rounded = new Date()
+ rounded.setMinutes(0)
+ rounded.setSeconds(0)
+ rounded.setMilliseconds(0)
+ return rounded.getTime()
+}
+
const LinkPreviewHead = ({ path }: Props) => {
- const url = `https://guild.xyz/api/linkpreview/${Date.now()?.toString()}/${path}`
+ const url = `https://guild.xyz/api/linkpreview/${getTimestamp()}/${path}`
return (
diff --git a/src/pages/api/linkpreview/[timestamp]/[guild]/[group].tsx b/src/pages/api/linkpreview/[[...params]]/index.tsx
similarity index 80%
rename from src/pages/api/linkpreview/[timestamp]/[guild]/[group].tsx
rename to src/pages/api/linkpreview/[[...params]]/index.tsx
index b50f4fb18b..8cae52a3d0 100644
--- a/src/pages/api/linkpreview/[timestamp]/[guild]/[group].tsx
+++ b/src/pages/api/linkpreview/[[...params]]/index.tsx
@@ -10,32 +10,34 @@ export const config = {
const interFont = loadGoogleFont("Inter", "400")
const interBoldFont = loadGoogleFont("Inter", "700")
const dystopianFont = fetch(
- new URL("../../../../../../public/fonts/Dystopian-Black.woff", import.meta.url)
+ new URL("../../../../../public/fonts/Dystopian-Black.woff", import.meta.url)
).then((res) => res.arrayBuffer())
const handler = async (req, _) => {
const { protocol, host } = req.nextUrl
const baseUrl = `${protocol}//${host}`
- const [, urlName, groupUrlName] =
+ const [, guildUrlName, pageUrlName] =
req.nextUrl?.pathname
?.replace("/api/linkpreview", "")
?.split("/")
?.filter((param) => !!param) ?? []
- if (!urlName || !groupUrlName) return new ImageResponse(<>>, { status: 404 })
+ if (!guildUrlName) return new Response(undefined, { status: 404 })
- const [guild, groups, guildRoles]: [Guild, Guild["groups"], Guild["roles"]] =
+ const [guild, guildRoles, pages]: [Guild, Guild["roles"], Guild["groups"]] =
await Promise.all([
- fetch(`${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${urlName}`).then(
- (res) => res.json()
- ),
fetch(
- `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${urlName}/groups`
+ `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${guildUrlName}`
).then((res) => res.json()),
fetch(
- `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${urlName}/roles`
+ `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${guildUrlName}/roles`
).then((res) => res.json()),
+ !!pageUrlName
+ ? fetch(
+ `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${guildUrlName}/groups`
+ ).then((res) => res.json())
+ : undefined,
]).catch(() => [null, null, null])
if (!guild?.id)
@@ -44,13 +46,13 @@ const handler = async (req, _) => {
statusText: "Guild not found",
})
- const group = groups?.find((g) => g.urlName === groupUrlName)
+ const page = pages?.find((p) => p.urlName === pageUrlName)
- if (!group)
- return new Response(undefined, {
- status: 404,
- statusText: "Group not found",
- })
+ const name = page?.name ?? guild.name
+ const image = page?.imageUrl || guild.imageUrl
+ const roles = !!page
+ ? guildRoles.filter((role) => role.groupId === page.id)
+ : guildRoles
try {
const [interFontData, interBoldFontData, dystopianFontData] = await Promise.all([
@@ -59,11 +61,9 @@ const handler = async (req, _) => {
dystopianFont,
])
- const roles = guildRoles?.map((role) => role.name)
-
- const safeGroupDescription = group.description?.replaceAll("\n", "")
-
- const imageUrl = group.imageUrl ?? guild.imageUrl
+ const safeDescription = (
+ !!page ? page.description : guild.description
+ )?.replaceAll("\n", "")
return new ImageResponse(
{
{/* eslint-disable-next-line @next/next/no-img-element */}
{
textOverflow: "ellipsis",
}}
>
- {group.name}
+ {name}
@@ -214,9 +214,9 @@ const handler = async (req, _) => {
color: "white",
}}
>
- {group.description ? (
- `${safeGroupDescription?.slice(0, 80)}${
- (safeGroupDescription?.length ?? 0) > 80 ? "..." : ""
+ {safeDescription ? (
+ `${safeDescription?.slice(0, 80)}${
+ safeDescription?.length > 80 ? "..." : ""
}`
) : (
@@ -260,6 +260,9 @@ const handler = async (req, _) => {
,
{
+ headers: {
+ "Cache-Control": "s-maxage=3600", // 1 hour
+ },
width: 800,
height: 450,
fonts: [
diff --git a/src/pages/api/linkpreview/[timestamp]/[guild]/index.tsx b/src/pages/api/linkpreview/[timestamp]/[guild]/index.tsx
deleted file mode 100644
index 59f32dabf9..0000000000
--- a/src/pages/api/linkpreview/[timestamp]/[guild]/index.tsx
+++ /dev/null
@@ -1,275 +0,0 @@
-import { env } from "env"
-import loadGoogleFont from "fonts/loadGoogleFont"
-import { ImageResponse } from "next/og"
-import { Guild } from "types"
-
-export const config = {
- runtime: "edge",
-}
-
-const interFont = loadGoogleFont("Inter", "400")
-const interBoldFont = loadGoogleFont("Inter", "700")
-const dystopianFont = fetch(
- new URL("../../../../../../public/fonts/Dystopian-Black.woff", import.meta.url)
-).then((res) => res.arrayBuffer())
-
-const handler = async (req, _) => {
- const { protocol, host } = req.nextUrl
- const baseUrl = `${protocol}//${host}`
-
- const [, urlName] =
- req.nextUrl?.pathname
- ?.replace("/api/linkpreview", "")
- ?.split("/")
- ?.filter((param) => !!param) ?? []
-
- if (!urlName) return new ImageResponse(<>>, { status: 404 })
-
- const [guild, guildRoles]: [Guild, Guild["roles"]] = await Promise.all([
- fetch(`${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${urlName}`).then(
- (res) => res.json()
- ),
- fetch(`${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds/${urlName}/roles`).then(
- (res) => res.json()
- ),
- ]).catch(() => [null, null])
-
- if (!guild?.id)
- return new Response(undefined, {
- status: 404,
- statusText: "Guild not found",
- })
-
- try {
- const [interFontData, interBoldFontData, dystopianFontData] = await Promise.all([
- interFont,
- interBoldFont,
- dystopianFont,
- ])
-
- const roles = guildRoles?.map((role) => role.name)
-
- const safeGuildDescription = guild.description?.replaceAll("\n", "")
-
- return new ImageResponse(
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
-
-
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
-
- {guild.name}
-
-
-
-
-
{`${new Intl.NumberFormat("en", { notation: "compact" }).format(
- guild?.memberCount ?? 0
- )} members`}
-
-
{`${roles?.length || 0} roles`}
-
-
-
- {guild.description ? (
- `${safeGuildDescription?.slice(0, 80)}${
- safeGuildDescription?.length > 80 ? "..." : ""
- }`
- ) : (
-
-
- {"That's a great party in there!"}
-
-
{"I dare you to be the plus one."}
-
- )}
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
- Guild.xyz
-
-
-
-
,
- {
- width: 800,
- height: 450,
- fonts: [
- {
- name: "Inter",
- data: interFontData,
- style: "normal",
- weight: 400,
- },
- {
- name: "Inter",
- data: interBoldFontData,
- style: "normal",
- weight: 700,
- },
- {
- name: "Dystopian",
- data: dystopianFontData,
- style: "normal",
- },
- ],
- }
- )
- } catch (e: any) {}
-}
-
-export default handler
diff --git a/src/pages/api/linkpreview/[timestamp]/index.tsx b/src/pages/api/linkpreview/[timestamp]/index.tsx
deleted file mode 100644
index ef63d658ac..0000000000
--- a/src/pages/api/linkpreview/[timestamp]/index.tsx
+++ /dev/null
@@ -1,343 +0,0 @@
-import { env } from "env"
-import loadGoogleFont from "fonts/loadGoogleFont"
-import { ImageResponse } from "next/og"
-import { GuildBase } from "types"
-
-export const config = {
- runtime: "edge",
-}
-
-const interFont = loadGoogleFont("Inter", "400")
-const dystopianFont = fetch(
- new URL("../../../../../public/fonts/Dystopian-Black.woff", import.meta.url)
-).then((res) => res.arrayBuffer())
-
-const handler = async (req, _) => {
- const { protocol, host } = req.nextUrl
- const baseUrl = `${protocol}//${host}`
-
- try {
- const [guilds, interFontData, dystopianFontData] = await Promise.all([
- fetch(`${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds`)
- .then((res) => res.json())
- .catch((_) => []),
- interFont,
- dystopianFont,
- ])
-
- return new ImageResponse(
-
-
- {guilds?.slice(0, 8).map((guild) => (
-
- ))}
-
-
-
-
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
- Guild
-
-
-
-
- Manage roles
-
-
- in your community
-
-
- based on tokens & NFTs
-
-
-
,
- {
- width: 800,
- height: 450,
- fonts: [
- {
- name: "Inter",
- data: interFontData,
- style: "normal",
- weight: 400,
- },
- {
- name: "Dystopian",
- data: dystopianFontData,
- style: "normal",
- },
- ],
- }
- )
- } catch (e: any) {
- return new Response(`Failed to generate the image`, {
- status: 500,
- })
- }
-}
-
-type GuildCardProps = {
- guild: GuildBase
- baseUrl: string
-}
-
-const GuildCard = ({ guild, baseUrl }: GuildCardProps): JSX.Element => (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
-
-
- {guild.name}
-
-
-
-
-
-
- {new Intl.NumberFormat("en", { notation: "compact" }).format(
- guild.memberCount ?? 0
- )}
-
-
-
-
- {`${guild.rolesCount} roles`}
-
-
-
-
-)
-
-export default handler