fix(web): fully remove payload runtime from production build
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Remove ALL Payload imports, withPayload wrapper, and (payload)
routes. Blog index + changelog are now static data arrays.
Blog post at /blog/peer-messaging-claude-code is static TSX.

Payload CMS stays as a dev dependency for future local admin
but has zero presence in the production build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-06 09:25:02 +01:00
parent 2be08ab85f
commit d8bafe3144
16 changed files with 3050 additions and 245 deletions

View File

@@ -1,14 +0,0 @@
import "@payloadcms/next/css";
import type { ReactNode } from "react";
export const metadata = {
title: "CMS — claudemesh",
};
export default function PayloadLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@@ -1,7 +0,0 @@
import { redirect } from "next/navigation";
// Payload admin disabled in production standalone output.
// Use local dev server for CMS admin.
export default function PayloadAdminRedirect() {
redirect("/");
}

View File

@@ -1 +0,0 @@
export const importMap = {};

View File

@@ -1,85 +0,0 @@
import { notFound } from "next/navigation";
export const dynamic = "force-dynamic";
type Props = { params: Promise<{ slug: string }> };
async function getPost(slug: string) {
try {
const { getPayload } = await import("payload");
const config = (await import("@payload-config")).default;
const payload = await getPayload({ config });
const { docs } = await payload.find({
collection: "posts",
where: { slug: { equals: slug }, status: { equals: "published" } },
limit: 1,
depth: 2,
});
return docs[0] ?? null;
} catch {
return null;
}
}
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) return { title: "Not found — claudemesh" };
return {
title: `${(post as any).title} — claudemesh`,
description: (post as any).excerpt || undefined,
};
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug) as any;
if (!post) notFound();
const author = typeof post.author === "object" ? post.author : null;
return (
<article className="mx-auto max-w-3xl px-6 py-24 md:py-32">
<header className="mb-12">
<time
dateTime={post.publishedAt}
className="text-[11px] uppercase tracking-wider text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-mono)" }}
>
{post.publishedAt
? new Date(post.publishedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
: "Draft"}
</time>
<h1
className="mt-3 text-[clamp(2rem,4.5vw,3rem)] font-medium leading-[1.1] text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
{post.title}
</h1>
{author && (
<p
className="mt-4 text-sm text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
by {author.name}{author.role ? ` · ${author.role}` : ""}
</p>
)}
</header>
<div
className="prose prose-invert max-w-none prose-headings:font-medium prose-a:text-[var(--cm-clay)] prose-a:no-underline hover:prose-a:underline prose-code:text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
{post.content && typeof post.content === "string" ? (
<div dangerouslySetInnerHTML={{ __html: post.content }} />
) : (
<p className="text-[var(--cm-fg-tertiary)]">Content not available.</p>
)}
</div>
</article>
);
}

View File

@@ -1,33 +1,21 @@
import Link from "next/link";
export const dynamic = "force-dynamic";
export const metadata = {
title: "Blog — claudemesh",
description: "Engineering notes on peer messaging, protocol design, and multi-agent security.",
};
async function getPosts() {
try {
const { getPayload } = await import("payload");
const config = (await import("@payload-config")).default;
const payload = await getPayload({ config });
const { docs } = await payload.find({
collection: "posts",
where: { status: { equals: "published" } },
sort: "-publishedAt",
limit: 20,
depth: 1,
});
return docs;
} catch {
return [];
}
}
export default async function BlogIndex() {
const posts = await getPosts();
const POSTS = [
{
slug: "peer-messaging-claude-code",
title: "Peer messaging for Claude Code: protocol, security, UX",
excerpt:
"How claudemesh connects Claude Code sessions over an encrypted mesh, using MCP dev-channels for real-time message injection.",
date: "2026-04-06",
},
];
export default function BlogIndex() {
return (
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
<h1
@@ -44,25 +32,18 @@ export default async function BlogIndex() {
</p>
<div className="mt-12 space-y-10">
{posts.length === 0 && (
<p className="text-sm text-[var(--cm-fg-tertiary)]" style={{ fontFamily: "var(--cm-font-mono)" }}>
No posts yet. First one ships soon.
</p>
)}
{posts.map((post: any) => (
<article key={post.id} className="border-b border-[var(--cm-border)] pb-8">
{POSTS.map((post) => (
<article key={post.slug} className="border-b border-[var(--cm-border)] pb-8">
<time
dateTime={post.publishedAt}
dateTime={post.date}
className="text-[11px] uppercase tracking-wider text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-mono)" }}
>
{post.publishedAt
? new Date(post.publishedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
: "Draft"}
{new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
<h2 className="mt-2">
<Link
@@ -73,14 +54,12 @@ export default async function BlogIndex() {
{post.title}
</Link>
</h2>
{post.excerpt && (
<p
className="mt-3 text-[14px] leading-[1.6] text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
{post.excerpt}
</p>
)}
<p
className="mt-3 text-[14px] leading-[1.6] text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
{post.excerpt}
</p>
</article>
))}
</div>

View File

@@ -1,43 +1,18 @@
export const dynamic = "force-dynamic";
export const metadata = {
title: "Changelog — claudemesh",
description: "Release history for claudemesh-cli.",
};
const TYPE_LABELS: Record<string, string> = {
feat: "Feature",
fix: "Fix",
docs: "Docs",
breaking: "Breaking",
};
const ENTRIES = [
{ version: "0.1.4", date: "2026-04-06", type: "feat", summary: "Stateful welcome screen, PROTOCOL.md, THREAT_MODEL.md, Windows CI matrix" },
{ version: "0.1.3", date: "2026-04-05", type: "feat", summary: "claudemesh --version, status, doctor commands" },
{ version: "0.1.2", date: "2026-04-05", type: "feat", summary: "claudemesh launch command, transparency banner, decrypt fix, Windows support" },
];
const TYPE_COLORS: Record<string, string> = {
feat: "bg-[var(--cm-clay)]",
fix: "bg-[var(--cm-cactus)]",
docs: "bg-[var(--cm-oat)]",
breaking: "bg-red-500",
};
async function getChangelog() {
try {
const { getPayload } = await import("payload");
const config = (await import("@payload-config")).default;
const payload = await getPayload({ config });
const { docs } = await payload.find({
collection: "changelog",
sort: "-date",
limit: 50,
});
return docs;
} catch {
return [];
}
}
export default async function ChangelogPage() {
const entries = await getChangelog();
const TYPE_LABELS: Record<string, string> = { feat: "Feature", fix: "Fix", docs: "Docs" };
const TYPE_COLORS: Record<string, string> = { feat: "bg-[var(--cm-clay)]", fix: "bg-[var(--cm-cactus)]", docs: "bg-[var(--cm-oat)]" };
export default function ChangelogPage() {
return (
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
<h1
@@ -52,18 +27,9 @@ export default async function ChangelogPage() {
>
Every shipped version of claudemesh-cli.
</p>
<div className="mt-12 space-y-8">
{entries.length === 0 && (
<p className="text-sm text-[var(--cm-fg-tertiary)]" style={{ fontFamily: "var(--cm-font-mono)" }}>
No entries yet.
</p>
)}
{entries.map((entry: any) => (
<article
key={entry.id}
className="border-b border-[var(--cm-border)] pb-6"
>
{ENTRIES.map((entry) => (
<article key={entry.version} className="border-b border-[var(--cm-border)] pb-6">
<div className="flex items-center gap-3">
<span
className={`rounded-[4px] px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-[var(--cm-bg)] ${TYPE_COLORS[entry.type] || "bg-[var(--cm-fg-tertiary)]"}`}
@@ -71,44 +37,16 @@ export default async function ChangelogPage() {
>
{TYPE_LABELS[entry.type] || entry.type}
</span>
<span
className="text-[18px] font-medium text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
<span className="text-[18px] font-medium text-[var(--cm-fg)]" style={{ fontFamily: "var(--cm-font-serif)" }}>
v{entry.version}
</span>
<time
dateTime={entry.date}
className="text-[11px] text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-mono)" }}
>
{new Date(entry.date).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})}
<time dateTime={entry.date} className="text-[11px] text-[var(--cm-fg-tertiary)]" style={{ fontFamily: "var(--cm-font-mono)" }}>
{new Date(entry.date).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })}
</time>
</div>
<p
className="mt-2 text-[14px] leading-[1.6] text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
<p className="mt-2 text-[14px] leading-[1.6] text-[var(--cm-fg-secondary)]" style={{ fontFamily: "var(--cm-font-sans)" }}>
{entry.summary}
</p>
{(entry.npmUrl || entry.githubUrl) && (
<div className="mt-3 flex gap-4 text-[12px]" style={{ fontFamily: "var(--cm-font-mono)" }}>
{entry.npmUrl && (
<a href={entry.npmUrl} className="text-[var(--cm-clay)] hover:underline">
npm
</a>
)}
{entry.githubUrl && (
<a href={entry.githubUrl} className="text-[var(--cm-clay)] hover:underline">
github
</a>
)}
</div>
)}
</article>
))}
</div>