- Connect homepage, blog, services, header, footer to Payload CMS via local API - Add HomePage global with 7 editorial sections (hero, painPoints, solution, whyAxil, audience, process, finalCta) - Add ServerHeader/ServerFooter async wrappers with unstable_cache + tag-based ISR revalidation - Rewrite blog/[slug] and services/[slug] pages to fetch from CMS with fallbacks - Add seed script (src/lib/seed.ts) to populate all collections and globals from hardcoded defaults - Restructure app into (site)/ route group to fix Payload admin hydration conflicts - Make root layout a passthrough; (site)/layout.tsx owns html/body/fonts - Restrict user creation/update/delete to admin role only - Fix migration imports: type MigrateUpArgs/Down from @payloadcms/db-postgres - Wrap revalidateTag dynamic imports in try/catch for seed/CLI compatibility - Skip migrations in dev mode (Payload handles schema push automatically) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
import 'server-only';
|
|
import { getPayload } from 'payload';
|
|
import config from '@payload-config';
|
|
import { unstable_cache } from 'next/cache';
|
|
import type { Post, Service, Testimonial, Navigation, Footer, HomePage } from '@/payload-types';
|
|
|
|
// ─── Singleton client ──────────────────────────────────────────────────────
|
|
|
|
let _client: Awaited<ReturnType<typeof getPayload>> | null = null;
|
|
|
|
export async function getPayloadClient() {
|
|
if (!_client) _client = await getPayload({ config });
|
|
return _client;
|
|
}
|
|
|
|
// ─── Navigation ────────────────────────────────────────────────────────────
|
|
|
|
export const getNavigation = unstable_cache(
|
|
async (): Promise<Navigation | null> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
return await payload.findGlobal({ slug: 'navigation' });
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
['navigation'],
|
|
{ tags: ['navigation'], revalidate: false },
|
|
);
|
|
|
|
// ─── Footer ────────────────────────────────────────────────────────────────
|
|
|
|
export const getFooter = unstable_cache(
|
|
async (): Promise<Footer | null> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
return await payload.findGlobal({ slug: 'footer' });
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
['footer'],
|
|
{ tags: ['footer'], revalidate: false },
|
|
);
|
|
|
|
// ─── Posts ─────────────────────────────────────────────────────────────────
|
|
|
|
export const getPublishedPosts = unstable_cache(
|
|
async (limit = 10): Promise<Post[]> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
const result = await payload.find({
|
|
collection: 'posts',
|
|
where: { status: { equals: 'published' } },
|
|
sort: '-publishedAt',
|
|
limit,
|
|
depth: 1,
|
|
});
|
|
return result.docs as Post[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
},
|
|
['posts'],
|
|
{ tags: ['posts'], revalidate: false },
|
|
);
|
|
|
|
export function getPostBySlugCached(slug: string): Promise<Post | undefined> {
|
|
return unstable_cache(
|
|
async () => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
const result = await payload.find({
|
|
collection: 'posts',
|
|
where: {
|
|
and: [{ slug: { equals: slug } }, { status: { equals: 'published' } }],
|
|
},
|
|
limit: 1,
|
|
depth: 1,
|
|
});
|
|
return result.docs[0] as Post | undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
},
|
|
[`post-${slug}`],
|
|
{ tags: ['posts', `post-${slug}`], revalidate: false },
|
|
)();
|
|
}
|
|
|
|
// ─── Services ──────────────────────────────────────────────────────────────
|
|
|
|
export const getPublishedServices = unstable_cache(
|
|
async (limit = 10): Promise<Service[]> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
const result = await payload.find({
|
|
collection: 'services',
|
|
where: { status: { equals: 'published' } },
|
|
sort: 'createdAt',
|
|
limit,
|
|
depth: 1,
|
|
});
|
|
return result.docs as Service[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
},
|
|
['services'],
|
|
{ tags: ['services'], revalidate: false },
|
|
);
|
|
|
|
export function getServiceBySlugCached(slug: string): Promise<Service | undefined> {
|
|
return unstable_cache(
|
|
async () => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
const result = await payload.find({
|
|
collection: 'services',
|
|
where: {
|
|
and: [{ slug: { equals: slug } }, { status: { equals: 'published' } }],
|
|
},
|
|
limit: 1,
|
|
depth: 2,
|
|
});
|
|
return result.docs[0] as Service | undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
},
|
|
[`service-${slug}`],
|
|
{ tags: ['services', `service-${slug}`], revalidate: false },
|
|
)();
|
|
}
|
|
|
|
// ─── HomePage Global ───────────────────────────────────────────────────────
|
|
|
|
export const getHomePage = unstable_cache(
|
|
async (): Promise<HomePage | null> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
return await payload.findGlobal({ slug: 'home-page' });
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
['homepage'],
|
|
{ tags: ['homepage'], revalidate: false },
|
|
);
|
|
|
|
// ─── Testimonials ──────────────────────────────────────────────────────────
|
|
|
|
export const getFeaturedTestimonials = unstable_cache(
|
|
async (limit = 8): Promise<Testimonial[]> => {
|
|
try {
|
|
const payload = await getPayloadClient();
|
|
const result = await payload.find({
|
|
collection: 'testimonials',
|
|
where: { featured: { equals: true } },
|
|
sort: '-publishedAt',
|
|
limit,
|
|
depth: 0,
|
|
});
|
|
return result.docs as Testimonial[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
},
|
|
['testimonials-featured'],
|
|
{ tags: ['testimonials'], revalidate: false },
|
|
);
|