fix(web): graceful fallback when payload db unavailable
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:
@@ -1,40 +1,39 @@
|
|||||||
import { notFound } from "next/navigation";
|
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";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
type Props = { params: Promise<{ slug: string }> };
|
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) {
|
export async function generateMetadata({ params }: Props) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const payload = await getPayload({ config });
|
const post = await getPost(slug);
|
||||||
const { docs } = await payload.find({
|
|
||||||
collection: "posts",
|
|
||||||
where: { slug: { equals: slug }, status: { equals: "published" } },
|
|
||||||
limit: 1,
|
|
||||||
depth: 1,
|
|
||||||
});
|
|
||||||
const post = docs[0];
|
|
||||||
if (!post) return { title: "Not found — claudemesh" };
|
if (!post) return { title: "Not found — claudemesh" };
|
||||||
return {
|
return {
|
||||||
title: `${post.title} — claudemesh`,
|
title: `${(post as any).title} — claudemesh`,
|
||||||
description: post.excerpt || post.seo?.metaDescription || undefined,
|
description: (post as any).excerpt || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function BlogPost({ params }: Props) {
|
export default async function BlogPost({ params }: Props) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const payload = await getPayload({ config });
|
const post = await getPost(slug) as any;
|
||||||
const { docs } = await payload.find({
|
|
||||||
collection: "posts",
|
|
||||||
where: { slug: { equals: slug }, status: { equals: "published" } },
|
|
||||||
limit: 1,
|
|
||||||
depth: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const post = docs[0] as any;
|
|
||||||
if (!post) notFound();
|
if (!post) notFound();
|
||||||
|
|
||||||
const author = typeof post.author === "object" ? post.author : null;
|
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)]"
|
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)" }}
|
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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getPayload } from "payload";
|
|
||||||
import config from "@payload-config";
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -9,15 +7,26 @@ export const metadata = {
|
|||||||
description: "Engineering notes on peer messaging, protocol design, and multi-agent security.",
|
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() {
|
export default async function BlogIndex() {
|
||||||
const payload = await getPayload({ config });
|
const posts = await getPosts();
|
||||||
const { docs: posts } = await payload.find({
|
|
||||||
collection: "posts",
|
|
||||||
where: { status: { equals: "published" } },
|
|
||||||
sort: "-publishedAt",
|
|
||||||
limit: 20,
|
|
||||||
depth: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
|
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import { getPayload } from "payload";
|
|
||||||
import config from "@payload-config";
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
@@ -22,13 +19,24 @@ const TYPE_COLORS: Record<string, string> = {
|
|||||||
breaking: "bg-red-500",
|
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() {
|
export default async function ChangelogPage() {
|
||||||
const payload = await getPayload({ config });
|
const entries = await getChangelog();
|
||||||
const { docs: entries } = await payload.find({
|
|
||||||
collection: "changelog",
|
|
||||||
sort: "-date",
|
|
||||||
limit: 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
|
<section className="mx-auto max-w-3xl px-6 py-24 md:py-32">
|
||||||
|
|||||||
Reference in New Issue
Block a user