diff --git a/.changeset/pretty-tables-melt.md b/.changeset/pretty-tables-melt.md
new file mode 100644
index 0000000..5ba907c
--- /dev/null
+++ b/.changeset/pretty-tables-melt.md
@@ -0,0 +1,5 @@
+---
+"stackspulse": patch
+---
+
+Add SEO to new tokens page.
diff --git a/apps/web/src/app/tokens/[token]/opengraph-image.tsx b/apps/web/src/app/tokens/[token]/opengraph-image.tsx
new file mode 100644
index 0000000..e6c4ac5
--- /dev/null
+++ b/apps/web/src/app/tokens/[token]/opengraph-image.tsx
@@ -0,0 +1,70 @@
+import { env } from "@/env";
+import { stacksTokensApi } from "@/lib/stacks";
+import { notFound } from "next/navigation";
+import { ImageResponse } from "next/og";
+
+export const runtime = "edge";
+
+export const alt = "About Protocol";
+export const size = {
+ width: 1012,
+ height: 506,
+};
+
+export const contentType = "image/png";
+
+interface PageProps {
+ params: { token: string };
+}
+
+export default async function Image({ params }: PageProps) {
+ const tokenInfo = await stacksTokensApi
+ .getFtMetadata(params.token)
+ .catch((error) => {
+ if (error.status === 404) {
+ return null;
+ }
+ throw error;
+ });
+ if (!tokenInfo) {
+ notFound();
+ }
+
+ return new ImageResponse(
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
![{alt}]({tokenInfo.image_thumbnail_uri)
+ {tokenInfo.symbol}
+
+
,
+ {
+ ...size,
+ },
+ );
+}
diff --git a/apps/web/src/app/tokens/[token]/page.tsx b/apps/web/src/app/tokens/[token]/page.tsx
index e89947d..7939853 100644
--- a/apps/web/src/app/tokens/[token]/page.tsx
+++ b/apps/web/src/app/tokens/[token]/page.tsx
@@ -4,6 +4,7 @@ import { TokenStats } from "@/components/Token/TokenStats";
import { TokenTransactionsVolume } from "@/components/Token/TokenTransactionsVolume";
import { stacksTokensApi } from "@/lib/stacks";
import { Container } from "@radix-ui/themes";
+import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { Suspense } from "react";
@@ -13,6 +14,30 @@ interface PageProps {
params: { token: string };
}
+export async function generateMetadata({
+ params,
+}: PageProps): Promise {
+ const tokenInfo = await stacksTokensApi
+ .getFtMetadata(params.token)
+ .catch((error) => {
+ if (error.status === 404) {
+ return null;
+ }
+ throw error;
+ });
+ if (!tokenInfo) {
+ notFound();
+ }
+
+ return {
+ title: `stackspulse - ${tokenInfo.name}`,
+ description: `Get the latest ${tokenInfo.name} on-chain stats. Explore holders, transaction volume, and more..`,
+ alternates: {
+ canonical: `/tokens/${params.token}`,
+ },
+ };
+}
+
export default async function ProtocolPage({ params }: PageProps) {
const tokenInfo = await stacksTokensApi
.getFtMetadata(params.token)