Render the header and footer from the root layout

Every page was importing Header and Footer and repeating the same store
name, logo, copyright and nav links. Hoisting both into the root layout
renders them once, and a new config/site.ts holds the values they shared.

The body becomes a flex column so the footer sits at the bottom of short
pages. not-found and error now render inside that chrome, so their
full-viewport centering drops to 60vh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1vsTA4MX6qFd7nqSAKtdV
This commit is contained in:
Rami Bitar
2026-08-05 15:03:44 -04:00
co-authored by Claude Opus 5
parent 8602e03079
commit ff4adba2fb
17 changed files with 174 additions and 309 deletions
+18 -24
View File
@@ -1,5 +1,3 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import AccountForm from '@/components/shopify/account-form';
export const metadata = { title: 'Activate your account — Shop' };
@@ -13,27 +11,23 @@ export default async function Page({
const { id, token } = await params;
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Activate your account"
description="Choose a password to finish setting up your account."
fields={[
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Activate account"
endpoint="/api/account/activate"
extraPayload={{ id, activationToken: token }}
redirectTo="/account"
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Activate your account"
description="Choose a password to finish setting up your account."
fields={[
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Activate account"
endpoint="/api/account/activate"
extraPayload={{ id, activationToken: token }}
redirectTo="/account"
/>
</main>
);
}
+29 -35
View File
@@ -1,44 +1,38 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Sign in — Shop' };
export default function Page() {
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Sign in"
fields={[
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'current-password',
},
]}
submitLabel="Sign in"
endpoint="/api/account/login"
redirectTo="/account"
footer={
<>
<span>
New here?{' '}
<AccountFormLink href="/account/register">
Create an account
</AccountFormLink>
</span>
<AccountFormLink href="/account/recover">
Forgot your password?
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Sign in"
fields={[
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'current-password',
},
]}
submitLabel="Sign in"
endpoint="/api/account/login"
redirectTo="/account"
footer={
<>
<span>
New here?{' '}
<AccountFormLink href="/account/register">
Create an account
</AccountFormLink>
</>
}
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
</span>
<AccountFormLink href="/account/recover">
Forgot your password?
</AccountFormLink>
</>
}
/>
</main>
);
}
+11 -19
View File
@@ -1,6 +1,4 @@
import { redirect } from 'next/navigation';
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import OrderHistory from '@/components/shopify/order-history';
import { getSessionToken } from '@/services/shopify/session';
import { getCustomer } from '@/services/shopify/customer';
@@ -16,23 +14,17 @@ export default async function Page() {
if (!customer) redirect('/account/login');
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-12">
<h1 className="text-2xl md:text-3xl font-normal text-foreground">
Order history
</h1>
<p className="mt-1 text-sm text-muted-foreground">
{customer.displayName} · {customer.email}
</p>
<main className="max-w-screen-2xl mx-auto w-full px-8 py-12">
<h1 className="text-2xl md:text-3xl font-normal text-foreground">
Order history
</h1>
<p className="mt-1 text-sm text-muted-foreground">
{customer.displayName} · {customer.email}
</p>
<div className="mt-10">
<OrderHistory customer={customer} />
</div>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
<div className="mt-10">
<OrderHistory customer={customer} />
</div>
</main>
);
}
+15 -21
View File
@@ -1,29 +1,23 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Reset password — Shop' };
export default function Page() {
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Reset password"
description="Enter your email and we'll send you a link to set a new password."
fields={[
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
]}
submitLabel="Send reset link"
endpoint="/api/account/recover"
successMessage="If that email has an account, a reset link is on its way."
footer={
<AccountFormLink href="/account/login">Back to sign in</AccountFormLink>
}
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Reset password"
description="Enter your email and we'll send you a link to set a new password."
fields={[
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
]}
submitLabel="Send reset link"
endpoint="/api/account/recover"
successMessage="If that email has an account, a reset link is on its way."
footer={
<AccountFormLink href="/account/login">Back to sign in</AccountFormLink>
}
/>
</main>
);
}
+35 -41
View File
@@ -1,49 +1,43 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Create account — Shop' };
export default function Page() {
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Create account"
fields={[
{
name: 'firstName',
label: 'First name',
autoComplete: 'given-name',
required: false,
},
{
name: 'lastName',
label: 'Last name',
autoComplete: 'family-name',
required: false,
},
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Create account"
endpoint="/api/account/register"
redirectTo="/account"
footer={
<span>
Already have an account?{' '}
<AccountFormLink href="/account/login">Sign in</AccountFormLink>
</span>
}
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Create account"
fields={[
{
name: 'firstName',
label: 'First name',
autoComplete: 'given-name',
required: false,
},
{
name: 'lastName',
label: 'Last name',
autoComplete: 'family-name',
required: false,
},
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
{
name: 'password',
label: 'Password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Create account"
endpoint="/api/account/register"
redirectTo="/account"
footer={
<span>
Already have an account?{' '}
<AccountFormLink href="/account/login">Sign in</AccountFormLink>
</span>
}
/>
</main>
);
}
+17 -23
View File
@@ -1,5 +1,3 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import AccountForm from '@/components/shopify/account-form';
export const metadata = { title: 'Set a new password — Shop' };
@@ -13,26 +11,22 @@ export default async function Page({
const { id, token } = await params;
return (
<>
<Header storeName="Shop" logoUrl="" />
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Set a new password"
fields={[
{
name: 'password',
label: 'New password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Save password"
endpoint="/api/account/reset"
extraPayload={{ id, resetToken: token }}
redirectTo="/account"
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<AccountForm
title="Set a new password"
fields={[
{
name: 'password',
label: 'New password',
type: 'password',
autoComplete: 'new-password',
},
]}
submitLabel="Save password"
endpoint="/api/account/reset"
extraPayload={{ id, resetToken: token }}
redirectTo="/account"
/>
</main>
);
}
+1 -19
View File
@@ -1,23 +1,5 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import CollectionDetail from '@/components/shopify/collection-detail';
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<CollectionDetail />
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
);
return <CollectionDetail />;
}
+1 -19
View File
@@ -1,23 +1,5 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Collections from '@/components/shopify/collections';
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<Collections title="Our Collections" />
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
);
return <Collections title="Our Collections" />;
}
+1 -1
View File
@@ -10,7 +10,7 @@ export default function Error({
reset: () => void;
}) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap">
Something went wrong
+13 -2
View File
@@ -1,7 +1,10 @@
import React from 'react';
import './globals.css';
import { Geist, Geist_Mono } from 'next/font/google';
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import StoreAssistant from '@/components/shopify/store-assistant';
import { site } from '@/config/site';
const geist = Geist({
subsets: ['latin'],
@@ -20,8 +23,16 @@ export default function RootLayout({
}) {
return (
<html lang="en" className={`${geist.variable} ${geistMono.variable}`}>
<body className="font-body antialiased bg-background text-foreground m-0 p-0">
{children}
<body className="font-body antialiased bg-background text-foreground m-0 p-0 flex min-h-screen flex-col">
{/* Header owns the cart drawer, search and account menu, so every
route gets them by rendering here rather than page by page. */}
<Header
storeName={site.storeName}
logoUrl={site.logoUrl}
links={site.navLinks}
/>
<div className="flex-1">{children}</div>
<Footer storeName={site.storeName} copyright={site.copyright} />
<StoreAssistant />
</body>
</html>
+1 -1
View File
@@ -5,7 +5,7 @@ import Link from 'next/link';
export default function NotFound() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap">
404
+4 -20
View File
@@ -1,26 +1,10 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Products from '@/components/shopify/products';
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<Products
title="Shopify Hydrogen Storefront"
subtitle="An agent-friendly Shopify storefront built with Next.js and Hydrogen."
/>
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
<Products
title="Shopify Hydrogen Storefront"
subtitle="An agent-friendly Shopify storefront built with Next.js and Hydrogen."
/>
);
}
+11 -29
View File
@@ -1,6 +1,4 @@
import { notFound } from 'next/navigation';
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import { getShopPolicy, POLICY_HANDLES } from '@/hooks/use-shopify-policies';
export function generateStaticParams() {
@@ -33,33 +31,17 @@ export default async function PolicyPage({
}
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<main className="max-w-screen-2xl mx-auto w-full px-5 lg:px-10 py-16">
<div className="max-w-2xl mx-auto">
<h1 className="text-3xl md:text-4xl font-normal text-foreground">
{policy.title}
</h1>
<main className="max-w-screen-2xl mx-auto w-full px-5 lg:px-10 py-16">
<div className="max-w-2xl mx-auto">
<h1 className="text-3xl md:text-4xl font-normal text-foreground">
{policy.title}
</h1>
<div
className="policy-body mt-8 text-[15px] leading-7 text-foreground"
dangerouslySetInnerHTML={{ __html: policy.body }}
/>
</div>
</main>
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
<div
className="policy-body mt-8 text-[15px] leading-7 text-foreground"
dangerouslySetInnerHTML={{ __html: policy.body }}
/>
</div>
</main>
);
}
-14
View File
@@ -1,25 +1,11 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import ProductDetail from '@/components/shopify/product-detail';
import ProductRecommendations from '@/components/shopify/product-recommendations';
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<ProductDetail addToCartLabel="Add to Cart" />
<ProductRecommendations title="You May Also Like" />
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
);
}
+4 -22
View File
@@ -1,6 +1,4 @@
import { Suspense } from 'react';
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import SearchResults from '@/components/shopify/search-results';
export const metadata = {
@@ -9,25 +7,9 @@ export const metadata = {
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
{/* useSearchParams needs a Suspense boundary to prerender this route. */}
<Suspense fallback={<div className="py-10" />}>
<SearchResults />
</Suspense>
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
// useSearchParams needs a Suspense boundary to prerender this route.
<Suspense fallback={<div className="py-10" />}>
<SearchResults />
</Suspense>
);
}
+1 -19
View File
@@ -1,23 +1,5 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Collections from '@/components/shopify/collections';
export default function Page() {
return (
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/shop/collections' },
]}
/>
<Collections title="Our Collections" />
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
);
return <Collections title="Our Collections" />;
}