diff --git a/src/app/(category-sidebar)/products/[category]/opengraph-image.tsx b/src/app/(category-sidebar)/products/[category]/opengraph-image.tsx new file mode 100644 index 0000000..fe90204 --- /dev/null +++ b/src/app/(category-sidebar)/products/[category]/opengraph-image.tsx @@ -0,0 +1,117 @@ +import { db } from "@/db"; +import { ImageResponse } from "next/og"; +import { notFound } from "next/navigation"; + +// Route segment config +export const runtime = "edge"; + +// Image metadata +export const alt = "About the category"; +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = "image/png"; + +// Image generation +export default async function Image(props: { + params: Promise<{ + category: string; + }>; +}) { + const { category: categoryParam } = await props.params; + const urlDecodedCategory = decodeURIComponent(categoryParam); + + const category = await db.query.categories.findFirst({ + where: (categories, { eq }) => eq(categories.slug, urlDecodedCategory), + with: { + subcollections: true, + }, + orderBy: (categories, { asc }) => asc(categories.name), + }); + + if (!category) { + return notFound(); + } + + const examples = category.subcollections + .slice(0, 2) + .map((s) => s.name) + .join(", "); + + const description = `Choose from our selection of ${category.name}, including ${examples + (category.subcollections.length > 1 ? "," : "")} and more. In stock and ready to ship.`; + + // TODO: Change design to add subcategory images that blur out + return new ImageResponse( + ( +
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {category.name} +
+
+

+ {category.name} +

+
+
+ {description} +
+
+
+ ), + { + width: 1200, + height: 630, + }, + ); +} diff --git a/src/app/(category-sidebar)/products/[category]/page.tsx b/src/app/(category-sidebar)/products/[category]/page.tsx index f71d240..6f7b307 100644 --- a/src/app/(category-sidebar)/products/[category]/page.tsx +++ b/src/app/(category-sidebar)/products/[category]/page.tsx @@ -6,10 +6,45 @@ import { subcollection, } from "@/db/schema"; import { count, eq } from "drizzle-orm"; +import { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ category: string }>; +}): Promise { + const { category: categoryParam } = await params; + const urlDecoded = decodeURIComponent(categoryParam); + const category = await db.query.categories.findFirst({ + where: (categories, { eq }) => eq(categories.slug, urlDecoded), + with: { + subcollections: true, + }, + orderBy: (categories, { asc }) => asc(categories.name), + }); + + if (!category) { + return notFound(); + } + + const examples = category.subcollections + .slice(0, 2) + .map((s) => s.name) + .join(", ") + .toLowerCase(); + + return { + title: `${category.name} | NextMaster`, + openGraph: { + title: `${category.name} | NextMaster`, + description: `Choose from our selection of ${category.name.toLowerCase()}, including ${examples + (category.subcollections.length > 1 ? "," : "")} and more. In stock and ready to ship.`, + }, + }; +} + export default async function Page(props: { params: Promise<{ category: string;