Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/web/src/app/chat/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ogCard, OG_SIZE } from "@/lib/og";

export const alt = "c0mpute chat: open models on the open compute network";
export const size = OG_SIZE;
export const contentType = "image/png";

export default function Image() {
return ogCard({
kicker: "open models, served by people's own hardware",
title: "Chat on c0mpute",
sub: "browser · CLI · OpenAI-compatible API · MCP",
});
}
8 changes: 6 additions & 2 deletions apps/web/src/app/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ChatApp } from "./chat-app";
import { pageOg } from "@/lib/og";

const DESCRIPTION =
"Chat with open models running on the c0mpute network. Sign in with CoinPay (DID required). Same chat from the browser, the c0mpute CLI, an OpenAI-compatible API and MCP.";

export const metadata = {
title: "chat — c0mpute",
description:
"Chat with open models running on the c0mpute network. Sign in with CoinPay (DID required). Same chat from the browser, the c0mpute CLI, an OpenAI-compatible API and MCP.",
description: DESCRIPTION,
alternates: { canonical: "https://c0mpute.com/chat" },
...pageOg("Chat with open models on c0mpute", DESCRIPTION, "/chat"),
};

export const dynamic = "force-dynamic";
Expand Down
13 changes: 13 additions & 0 deletions apps/web/src/app/chat/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ogCard, OG_SIZE } from "@/lib/og";

export const alt = "c0mpute chat: open models on the open compute network";
export const size = OG_SIZE;
export const contentType = "image/png";

export default function Image() {
return ogCard({
kicker: "open models, served by people's own hardware",
title: "Chat on c0mpute",
sub: "browser · CLI · OpenAI-compatible API · MCP",
});
}
3 changes: 1 addition & 2 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const DESCRIPTION =
export const revalidate = 3600;

export const metadata: Metadata = {
metadataBase: new URL(SITE),
title: "c0mpute — the open compute network",
description: DESCRIPTION,
manifest: "/manifest.json",
Expand All @@ -25,13 +26,11 @@ export const metadata: Metadata = {
title: "c0mpute — the open compute network",
description: DESCRIPTION,
url: SITE,
images: [{ url: `${SITE}/og-image.png`, width: 1200, height: 630, alt: "c0mpute" }],
},
twitter: {
card: "summary_large_image",
title: "c0mpute — the open compute network",
description: DESCRIPTION,
images: [`${SITE}/og-image.png`],
},
alternates: { canonical: SITE },
};
Expand Down
19 changes: 19 additions & 0 deletions apps/web/src/app/nodes/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ogCard, OG_SIZE } from "@/lib/og";
import { getNodesPayload } from "@/lib/nodes";

export const alt = "c0mpute nodes online";
export const size = OG_SIZE;
export const contentType = "image/png";
export const dynamic = "force-dynamic";

export default async function Image() {
const p = await getNodesPayload();
return ogCard({
kicker: "every node online now",
title:
p.source === "aggregator" && p.workers_online > 0
? `${p.workers_online.toLocaleString()} nodes online`
: "The c0mpute node board",
sub: "goal: 100,000 nodes",
});
}
8 changes: 6 additions & 2 deletions apps/web/src/app/nodes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import Link from "next/link";
import { CodeBlock } from "@/components/CodeBlock";
import { getNodesPayload, NODE_GOAL } from "@/lib/nodes";
import { pageOg } from "@/lib/og";

const DESCRIPTION =
"Every c0mpute node online right now, ranked by its c0mpute bench score, with who runs each one and who invited them. How to name your node. The goal is 100,000 nodes.";

export const metadata = {
title: "nodes — c0mpute",
description:
"Every c0mpute node online right now, ranked by its c0mpute bench score, with who runs each one and who invited them. How to name your node. The goal is 100,000 nodes.",
description: DESCRIPTION,
alternates: { canonical: "https://c0mpute.com/nodes" },
...pageOg("c0mpute nodes online", DESCRIPTION, "/nodes"),
};

// A live board must not be a build-time snapshot. The aggregator read is a
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/app/nodes/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ogCard, OG_SIZE } from "@/lib/og";
import { getNodesPayload } from "@/lib/nodes";

export const alt = "c0mpute nodes online";
export const size = OG_SIZE;
export const contentType = "image/png";
export const dynamic = "force-dynamic";

export default async function Image() {
const p = await getNodesPayload();
return ogCard({
kicker: "every node online now",
title: `${p.workers_online.toLocaleString()} nodes online`,
sub: "goal: 100,000 nodes",
});
}
13 changes: 13 additions & 0 deletions apps/web/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ogCard, OG_SIZE } from "@/lib/og";

export const alt = "c0mpute: the open compute network";
export const size = OG_SIZE;
export const contentType = "image/png";

export default function Image() {
return ogCard({
kicker: "run jobs anywhere, sell unused compute",
title: "The open compute network",
sub: "CPUs + GPUs from PCs to data centers",
});
}
15 changes: 15 additions & 0 deletions apps/web/src/app/r/[code]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ogCard, OG_SIZE } from "@/lib/og";
import { sanitizeRef } from "@/lib/nodes";

export const alt = "Run a c0mpute node";
export const size = OG_SIZE;
export const contentType = "image/png";

export default async function Image({ params }: { params: Promise<{ code: string }> }) {
const code = sanitizeRef((await params).code);
return ogCard({
kicker: code ? `${code} invited you` : "you're invited",
title: "Run a c0mpute node",
sub: "one command · CPU or GPU · goal: 100,000 nodes",
});
}
10 changes: 7 additions & 3 deletions apps/web/src/app/r/[code]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { CodeBlock } from "@/components/CodeBlock";
import { installLine, sanitizeRef } from "@/lib/nodes";
import { pageOg } from "@/lib/og";

type Props = { params: Promise<{ code: string }> };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const code = sanitizeRef((await params).code);
const title = code ? `${code} invited you to run a c0mpute node` : "Run a c0mpute node";
const description =
"Run a c0mpute node with one command. The open compute network is aiming for 100,000 nodes so it can train and serve open-weight models in the open.";
return {
title: code ? `${code} invited you to run a c0mpute node` : "c0mpute",
description:
"Run a c0mpute node with one command. The open compute network is aiming for 100,000 nodes so it can train and serve open-weight models in the open.",
title,
description,
robots: { index: false, follow: true },
alternates: { canonical: "https://c0mpute.com/" },
...pageOg(title, description, `/r/${code ?? ""}`),
};
}

Expand Down
15 changes: 15 additions & 0 deletions apps/web/src/app/r/[code]/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ogCard, OG_SIZE } from "@/lib/og";
import { sanitizeRef } from "@/lib/nodes";

export const alt = "Run a c0mpute node";
export const size = OG_SIZE;
export const contentType = "image/png";

export default async function Image({ params }: { params: Promise<{ code: string }> }) {
const code = sanitizeRef((await params).code);
return ogCard({
kicker: code ? `${code} invited you` : "you're invited",
title: "Run a c0mpute node",
sub: "one command · CPU or GPU · goal: 100,000 nodes",
});
}
13 changes: 13 additions & 0 deletions apps/web/src/app/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ogCard, OG_SIZE } from "@/lib/og";

export const alt = "c0mpute: the open compute network";
export const size = OG_SIZE;
export const contentType = "image/png";

export default function Image() {
return ogCard({
kicker: "run jobs anywhere, sell unused compute",
title: "The open compute network",
sub: "CPUs + GPUs from PCs to data centers",
});
}
54 changes: 54 additions & 0 deletions apps/web/src/lib/og.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ImageResponse } from "next/og";
import type { Metadata } from "next";

export const SITE = "https://c0mpute.com";
export const OG_SIZE = { width: 1200, height: 630 };

/**
* Open Graph for one page. Next replaces the layout's `openGraph` object
* wholesale when a page sets its own, so every page that wants its own title
* goes through here to keep type/site name. The image comes from the nearest
* `opengraph-image.tsx`, never from a static file (the old /og-image.png 404ed
* and left every social preview blank).
*/
export function pageOg(title: string, description: string, path: string): Pick<Metadata, "openGraph" | "twitter"> {
return {
openGraph: { type: "website", siteName: "c0mpute", title, description, url: `${SITE}${path}` },
twitter: { card: "summary_large_image", title, description },
};
}

/** The c0mpute card: dark terminal, green accent, a kicker, a title, a footer line. */
export function ogCard({ kicker, title, sub }: { kicker: string; title: string; sub: string }) {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: "64px 72px",
background: "#0a0a0b",
color: "#d4d4d4",
fontFamily: "monospace",
borderTop: "10px solid #4ade80",
}}
>
<div style={{ display: "flex", fontSize: 40, fontWeight: 700, color: "#4ade80" }}>c0mpute</div>
<div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
<div style={{ display: "flex", fontSize: 30, color: "#6b7280" }}>{`// ${kicker}`}</div>
<div style={{ display: "flex", fontSize: title.length > 40 ? 64 : 76, fontWeight: 700, lineHeight: 1.1, color: "#f5f5f5" }}>
{title}
</div>
</div>
<div style={{ display: "flex", justifyContent: "space-between", fontSize: 28, color: "#6b7280" }}>
<span>{sub}</span>
<span style={{ color: "#4ade80" }}>c0mpute.com</span>
</div>
</div>
),
OG_SIZE,
);
}
Loading