Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/app/[country]/[locale]/(storefront)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Metadata } from "next";
import Link from "next/link";
import { CheckIcon, LightningBoltIcon, SupportIcon } from "@/components/icons";
import { generateHomeMetadata } from "@/lib/metadata/home";
import { FeaturedProducts } from "./FeaturedProducts";

interface HomePageProps {
Expand All @@ -9,6 +11,13 @@ interface HomePageProps {
}>;
}

export async function generateMetadata({
params,
}: HomePageProps): Promise<Metadata> {
const { country, locale } = await params;
return generateHomeMetadata({ country, locale });
}

export default async function HomePage({ params }: HomePageProps) {
const { country, locale } = await params;
const basePath = `/${country}/${locale}`;
Expand Down
43 changes: 42 additions & 1 deletion src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import type { Metadata } from "next";
import { JsonLd } from "@/components/seo/JsonLd";
import { getCachedProduct, getCachedStore } from "@/lib/data/cached";
import { generateProductMetadata } from "@/lib/metadata/product";
import { buildCanonicalUrl, buildProductJsonLd } from "@/lib/seo";
import { ProductDetailsWrapper } from "./ProductDetailsWrapper";

interface ProductPageProps {
Expand All @@ -8,9 +13,45 @@ interface ProductPageProps {
}>;
}

export async function generateMetadata({
params,
}: ProductPageProps): Promise<Metadata> {
const { country, locale, slug } = await params;
return generateProductMetadata({ country, locale, slug });
}

export default async function ProductPage({ params }: ProductPageProps) {
const { country, locale, slug } = await params;
const basePath = `/${country}/${locale}`;

return <ProductDetailsWrapper slug={slug} basePath={basePath} />;
let product;
try {
product = await getCachedProduct(slug, "images", locale);
} catch {
product = null;
}

let store;
try {
store = await getCachedStore(locale);
} catch {
store = null;
}

const canonicalUrl =
product && store?.url
? buildCanonicalUrl(
store.url,
`/${country}/${locale}/products/${product.slug}`,
)
: undefined;

return (
<>
{product && canonicalUrl && (
<JsonLd data={buildProductJsonLd(product, canonicalUrl)} />
)}
<ProductDetailsWrapper slug={slug} basePath={basePath} />
</>
);
}
9 changes: 9 additions & 0 deletions src/app/[country]/[locale]/(storefront)/products/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Metadata } from "next";
import { generateProductsMetadata } from "@/lib/metadata/products";
import { ProductsContent } from "./ProductsContent";

interface ProductsPageProps {
Expand All @@ -7,6 +9,13 @@ interface ProductsPageProps {
}>;
}

export async function generateMetadata({
params,
}: ProductsPageProps): Promise<Metadata> {
const { country, locale } = await params;
return generateProductsMetadata({ country, locale });
}

export default async function ProductsPage({ params }: ProductsPageProps) {
const { country, locale } = await params;
const basePath = `/${country}/${locale}`;
Expand Down
26 changes: 24 additions & 2 deletions src/app/[country]/[locale]/(storefront)/t/[...permalink]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { Breadcrumbs } from "@/components/navigation/Breadcrumbs";
import { getTaxon } from "@/lib/data/taxonomies";
import { JsonLd } from "@/components/seo/JsonLd";
import { getCachedStore, getCachedTaxon } from "@/lib/data/cached";
import { generateCategoryMetadata } from "@/lib/metadata/category";
import { buildBreadcrumbJsonLd } from "@/lib/seo";
import { CategoryProductsContent } from "./CategoryProductsContent";

export const revalidate = 60;
Expand All @@ -13,14 +17,21 @@ interface CategoryPageProps {
}>;
}

export async function generateMetadata({
params,
}: CategoryPageProps): Promise<Metadata> {
const { country, locale, permalink } = await params;
return generateCategoryMetadata({ country, locale, permalink });
}

export default async function CategoryPage({ params }: CategoryPageProps) {
const { country, locale, permalink } = await params;
const fullPermalink = permalink.join("/");
const basePath = `/${country}/${locale}`;

let taxon;
try {
taxon = await getTaxon(fullPermalink, { includes: "ancestors,children" });
taxon = await getCachedTaxon(fullPermalink, "ancestors,children", locale);
} catch (error) {
console.error("Failed to fetch taxon:", error);
notFound();
Expand All @@ -30,8 +41,19 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
notFound();
}

let store;
try {
store = await getCachedStore(locale);
} catch {
store = null;
}

return (
<div>
{store?.url && (
<JsonLd data={buildBreadcrumbJsonLd(taxon, basePath, store.url)} />
)}

{/* Banner Image */}
{taxon.image_url && (
<div className="relative w-full h-48 md:h-64 lg:h-80 bg-gray-100">
Expand Down
9 changes: 9 additions & 0 deletions src/app/[country]/[locale]/(storefront)/taxonomies/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { StoreTaxon, StoreTaxonomy } from "@spree/sdk";
import type { Metadata } from "next";
import Link from "next/link";
import { GridIcon } from "@/components/icons";
import { getTaxonomies } from "@/lib/data/taxonomies";
import { generateCategoriesMetadata } from "@/lib/metadata/categories";

export const revalidate = 60;

Expand All @@ -12,6 +14,13 @@ interface CategoriesPageProps {
}>;
}

export async function generateMetadata({
params,
}: CategoriesPageProps): Promise<Metadata> {
const { country, locale } = await params;
return generateCategoriesMetadata({ country, locale });
}

function getTopLevelTaxons(taxons: StoreTaxon[] | undefined): StoreTaxon[] {
if (!taxons) return [];
// Filter to only show depth 1 taxons (direct children of root)
Expand Down
24 changes: 23 additions & 1 deletion src/app/[country]/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { JsonLd } from "@/components/seo/JsonLd";
import { AuthProvider } from "@/contexts/AuthContext";
import { StoreProvider } from "@/contexts/StoreContext";
import { getCachedStore } from "@/lib/data/cached";
import { generateStoreMetadata } from "@/lib/metadata/store";
import { buildOrganizationJsonLd } from "@/lib/seo";

interface CountryLocaleLayoutProps {
children: React.ReactNode;
Expand All @@ -9,15 +14,32 @@ interface CountryLocaleLayoutProps {
}>;
}

export async function generateMetadata({
params,
}: CountryLocaleLayoutProps): Promise<Metadata> {
const { locale } = await params;
return generateStoreMetadata({ locale });
}

export default async function CountryLocaleLayout({
children,
params,
}: CountryLocaleLayoutProps) {
const { country, locale } = await params;

let store;
try {
store = await getCachedStore(locale);
} catch {
store = null;
}

return (
<StoreProvider initialCountry={country} initialLocale={locale}>
<AuthProvider>{children}</AuthProvider>
<AuthProvider>
{store && <JsonLd data={buildOrganizationJsonLd(store)} />}
{children}
</AuthProvider>
</StoreProvider>
);
}
7 changes: 5 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ const geist = Geist({
});

export const metadata: Metadata = {
title: "Spree Storefront",
description: "Next.js storefront powered by Spree Commerce",
title: {
template: "%s | Spree Store",
default: "Spree Store",
},
description: "Online store powered by Spree Commerce",
};

export default function RootLayout({
Expand Down
14 changes: 14 additions & 0 deletions src/components/seo/JsonLd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
interface JsonLdProps {
data: Record<string, unknown>;
}

export function JsonLd({ data }: JsonLdProps) {
const serialized = JSON.stringify(data).replaceAll("<", "\\u003c");

return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: serialized }}
/>
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
14 changes: 14 additions & 0 deletions src/lib/data/cached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getProduct, getStore, getTaxon } from "@spree/next";
import { cache } from "react";

export const getCachedProduct = cache(
(slugOrId: string, includes: string, locale: string) =>
getProduct(slugOrId, { includes }, { locale }),
);

export const getCachedTaxon = cache(
(idOrPermalink: string, includes: string, locale: string) =>
getTaxon(idOrPermalink, { includes }, { locale }),
);

export const getCachedStore = cache((locale: string) => getStore({ locale }));
36 changes: 36 additions & 0 deletions src/lib/metadata/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Metadata } from "next";
import { getCachedStore } from "@/lib/data/cached";
import { buildCanonicalUrl } from "@/lib/seo";

interface CategoriesMetadataParams {
country: string;
locale: string;
}

export async function generateCategoriesMetadata({
country,
locale,
}: CategoriesMetadataParams): Promise<Metadata> {
let store;
try {
store = await getCachedStore(locale);
} catch {
store = null;
}

const canonicalUrl = store?.url
? buildCanonicalUrl(store.url, `/${country}/${locale}/taxonomies`)
: undefined;

return {
title: "Categories",
description: "Browse all product categories.",
...(canonicalUrl ? { alternates: { canonical: canonicalUrl } } : {}),
openGraph: {
title: "Categories",
description: "Browse all product categories.",
...(canonicalUrl ? { url: canonicalUrl } : {}),
type: "website",
},
};
}
57 changes: 57 additions & 0 deletions src/lib/metadata/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Metadata } from "next";
import { getCachedStore, getCachedTaxon } from "@/lib/data/cached";
import { buildCanonicalUrl } from "@/lib/seo";

interface CategoryMetadataParams {
country: string;
locale: string;
permalink: string[];
}

export async function generateCategoryMetadata({
country,
locale,
permalink,
}: CategoryMetadataParams): Promise<Metadata> {
const fullPermalink = permalink.join("/");

let taxon;
try {
taxon = await getCachedTaxon(fullPermalink, "ancestors,children", locale);
} catch {
return { title: "Category Not Found" };
}

let store;
try {
store = await getCachedStore(locale);
} catch {
store = null;
}

const title = taxon.meta_title || taxon.name;
const description =
taxon.meta_description ||
taxon.description ||
`Browse ${taxon.name} products.`;
Comment thread
Cichorek marked this conversation as resolved.
Outdated

const canonicalUrl = store?.url
? buildCanonicalUrl(store.url, `/${country}/${locale}/t/${taxon.permalink}`)
: undefined;

return {
title,
description,
...(taxon.meta_keywords ? { keywords: taxon.meta_keywords } : {}),
...(canonicalUrl ? { alternates: { canonical: canonicalUrl } } : {}),
openGraph: {
title,
description,
...(canonicalUrl ? { url: canonicalUrl } : {}),
type: "website",
...(taxon.image_url
? { images: [{ url: taxon.image_url, alt: taxon.name }] }
: {}),
},
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading