fix(web): graceful fallback when payload db unavailable
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

Production has no SQLite — Payload pages now catch connection
errors and render empty state instead of crashing with React #130.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-06 01:21:04 +01:00
parent 8de952d91b
commit b758fe07ff
3 changed files with 62 additions and 42 deletions

View File

@@ -1,40 +1,39 @@
import { notFound } from "next/navigation";
import { getPayload } from "payload";
import config from "@payload-config";
import { RichText } from "@payloadcms/richtext-lexical/react";
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 payload = await getPayload({ config });
const { docs } = await payload.find({
collection: "posts",
where: { slug: { equals: slug }, status: { equals: "published" } },
limit: 1,
depth: 1,
});
const post = docs[0];
const post = await getPost(slug);
if (!post) return { title: "Not found — claudemesh" };
return {
title: `${post.title} — claudemesh`,
description: post.excerpt || post.seo?.metaDescription || undefined,
title: `${(post as any).title} — claudemesh`,
description: (post as any).excerpt || undefined,
};
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
const payload = await getPayload({ config });
const { docs } = await payload.find({
collection: "posts",
where: { slug: { equals: slug }, status: { equals: "published" } },
limit: 1,
depth: 2,
});
const post = docs[0] as any;
const post = await getPost(slug) as any;
if (!post) notFound();
const author = typeof post.author === "object" ? post.author : null;
@@ -75,7 +74,11 @@ export default async function BlogPost({ params }: Props) {
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 && <RichText data={post.content} />}
{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,6 +1,4 @@
import Link from "next/link";
import { getPayload } from "payload";
import config from "@payload-config";
export const dynamic = "force-dynamic";
@@ -9,15 +7,26 @@ export const metadata = {
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 payload = await getPayload({ config });
const { docs: posts } = await payload.find({
collection: "posts",
where: { status: { equals: "published" } },
sort: "-publishedAt",
limit: 20,
depth: 1,
});
const posts = await getPosts();
return (
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">

View File

@@ -1,6 +1,3 @@
import { getPayload } from "payload";
import config from "@payload-config";
export const dynamic = "force-dynamic";
export const metadata = {
@@ -22,13 +19,24 @@ const TYPE_COLORS: Record<string, string> = {
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 payload = await getPayload({ config });
const { docs: entries } = await payload.find({
collection: "changelog",
sort: "-date",
limit: 50,
});
const entries = await getChangelog();
return (
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">