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'; import AccountForm from '@/components/shopify/account-form';
export const metadata = { title: 'Activate your account — Shop' }; export const metadata = { title: 'Activate your account — Shop' };
@@ -13,27 +11,23 @@ export default async function Page({
const { id, token } = await params; const { id, token } = await params;
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<Header storeName="Shop" logoUrl="" /> <AccountForm
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16"> title="Activate your account"
<AccountForm description="Choose a password to finish setting up your account."
title="Activate your account" fields={[
description="Choose a password to finish setting up your account." {
fields={[ name: 'password',
{ label: 'Password',
name: 'password', type: 'password',
label: 'Password', autoComplete: 'new-password',
type: 'password', },
autoComplete: 'new-password', ]}
}, submitLabel="Activate account"
]} endpoint="/api/account/activate"
submitLabel="Activate account" extraPayload={{ id, activationToken: token }}
endpoint="/api/account/activate" redirectTo="/account"
extraPayload={{ id, activationToken: token }} />
redirectTo="/account" </main>
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
); );
} }
+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'; import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Sign in — Shop' }; export const metadata = { title: 'Sign in — Shop' };
export default function Page() { export default function Page() {
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<Header storeName="Shop" logoUrl="" /> <AccountForm
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16"> title="Sign in"
<AccountForm fields={[
title="Sign in" { name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
fields={[ {
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' }, name: 'password',
{ label: 'Password',
name: 'password', type: 'password',
label: 'Password', autoComplete: 'current-password',
type: 'password', },
autoComplete: 'current-password', ]}
}, submitLabel="Sign in"
]} endpoint="/api/account/login"
submitLabel="Sign in" redirectTo="/account"
endpoint="/api/account/login" footer={
redirectTo="/account" <>
footer={ <span>
<> New here?{' '}
<span> <AccountFormLink href="/account/register">
New here?{' '} Create an account
<AccountFormLink href="/account/register">
Create an account
</AccountFormLink>
</span>
<AccountFormLink href="/account/recover">
Forgot your password?
</AccountFormLink> </AccountFormLink>
</> </span>
} <AccountFormLink href="/account/recover">
/> Forgot your password?
</main> </AccountFormLink>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." /> </>
</> }
/>
</main>
); );
} }
+11 -19
View File
@@ -1,6 +1,4 @@
import { redirect } from 'next/navigation'; 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 OrderHistory from '@/components/shopify/order-history';
import { getSessionToken } from '@/services/shopify/session'; import { getSessionToken } from '@/services/shopify/session';
import { getCustomer } from '@/services/shopify/customer'; import { getCustomer } from '@/services/shopify/customer';
@@ -16,23 +14,17 @@ export default async function Page() {
if (!customer) redirect('/account/login'); if (!customer) redirect('/account/login');
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-12">
<Header storeName="Shop" logoUrl="" /> <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"> <div className="mt-10">
<h1 className="text-2xl md:text-3xl font-normal text-foreground"> <OrderHistory customer={customer} />
Order history </div>
</h1> </main>
<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." />
</>
); );
} }
+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'; import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Reset password — Shop' }; export const metadata = { title: 'Reset password — Shop' };
export default function Page() { export default function Page() {
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<Header storeName="Shop" logoUrl="" /> <AccountForm
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16"> title="Reset password"
<AccountForm description="Enter your email and we'll send you a link to set a new password."
title="Reset password" fields={[
description="Enter your email and we'll send you a link to set a new password." { name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
fields={[ ]}
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' }, submitLabel="Send reset link"
]} endpoint="/api/account/recover"
submitLabel="Send reset link" successMessage="If that email has an account, a reset link is on its way."
endpoint="/api/account/recover" footer={
successMessage="If that email has an account, a reset link is on its way." <AccountFormLink href="/account/login">Back to sign in</AccountFormLink>
footer={ }
<AccountFormLink href="/account/login">Back to sign in</AccountFormLink> />
} </main>
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
); );
} }
+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'; import AccountForm, { AccountFormLink } from '@/components/shopify/account-form';
export const metadata = { title: 'Create account — Shop' }; export const metadata = { title: 'Create account — Shop' };
export default function Page() { export default function Page() {
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<Header storeName="Shop" logoUrl="" /> <AccountForm
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16"> title="Create account"
<AccountForm fields={[
title="Create account" {
fields={[ name: 'firstName',
{ label: 'First name',
name: 'firstName', autoComplete: 'given-name',
label: 'First name', required: false,
autoComplete: 'given-name', },
required: false, {
}, name: 'lastName',
{ label: 'Last name',
name: 'lastName', autoComplete: 'family-name',
label: 'Last name', required: false,
autoComplete: 'family-name', },
required: false, { name: 'email', label: 'Email', type: 'email', autoComplete: 'email' },
}, {
{ name: 'email', label: 'Email', type: 'email', autoComplete: 'email' }, name: 'password',
{ label: 'Password',
name: 'password', type: 'password',
label: 'Password', autoComplete: 'new-password',
type: 'password', },
autoComplete: 'new-password', ]}
}, submitLabel="Create account"
]} endpoint="/api/account/register"
submitLabel="Create account" redirectTo="/account"
endpoint="/api/account/register" footer={
redirectTo="/account" <span>
footer={ Already have an account?{' '}
<span> <AccountFormLink href="/account/login">Sign in</AccountFormLink>
Already have an account?{' '} </span>
<AccountFormLink href="/account/login">Sign in</AccountFormLink> }
</span> />
} </main>
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
); );
} }
+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'; import AccountForm from '@/components/shopify/account-form';
export const metadata = { title: 'Set a new password — Shop' }; export const metadata = { title: 'Set a new password — Shop' };
@@ -13,26 +11,22 @@ export default async function Page({
const { id, token } = await params; const { id, token } = await params;
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-8 py-16">
<Header storeName="Shop" logoUrl="" /> <AccountForm
<main className="max-w-screen-2xl mx-auto w-full px-8 py-16"> title="Set a new password"
<AccountForm fields={[
title="Set a new password" {
fields={[ name: 'password',
{ label: 'New password',
name: 'password', type: 'password',
label: 'New password', autoComplete: 'new-password',
type: 'password', },
autoComplete: 'new-password', ]}
}, submitLabel="Save password"
]} endpoint="/api/account/reset"
submitLabel="Save password" extraPayload={{ id, resetToken: token }}
endpoint="/api/account/reset" redirectTo="/account"
extraPayload={{ id, resetToken: token }} />
redirectTo="/account" </main>
/>
</main>
<Footer storeName="Shop" copyright="© 2026 Shop. All rights reserved." />
</>
); );
} }
+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'; import CollectionDetail from '@/components/shopify/collection-detail';
export default function Page() { export default function Page() {
return ( return <CollectionDetail />;
<>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<CollectionDetail />
<Footer
storeName="Shop"
copyright="© 2026 Shop. All rights reserved."
/>
</>
);
} }
+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'; import Collections from '@/components/shopify/collections';
export default function Page() { export default function Page() {
return ( return <Collections title="Our Collections" />;
<>
<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."
/>
</>
);
} }
+1 -1
View File
@@ -10,7 +10,7 @@ export default function Error({
reset: () => void; reset: () => void;
}) { }) {
return ( 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="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap"> <div className="text-lg font-medium text-black font-sans whitespace-nowrap">
Something went wrong Something went wrong
+13 -2
View File
@@ -1,7 +1,10 @@
import React from 'react'; import React from 'react';
import './globals.css'; import './globals.css';
import { Geist, Geist_Mono } from 'next/font/google'; 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 StoreAssistant from '@/components/shopify/store-assistant';
import { site } from '@/config/site';
const geist = Geist({ const geist = Geist({
subsets: ['latin'], subsets: ['latin'],
@@ -20,8 +23,16 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en" className={`${geist.variable} ${geistMono.variable}`}> <html lang="en" className={`${geist.variable} ${geistMono.variable}`}>
<body className="font-body antialiased bg-background text-foreground m-0 p-0"> <body className="font-body antialiased bg-background text-foreground m-0 p-0 flex min-h-screen flex-col">
{children} {/* 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 /> <StoreAssistant />
</body> </body>
</html> </html>
+1 -1
View File
@@ -5,7 +5,7 @@ import Link from 'next/link';
export default function NotFound() { export default function NotFound() {
return ( 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="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap"> <div className="text-lg font-medium text-black font-sans whitespace-nowrap">
404 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'; import Products from '@/components/shopify/products';
export default function Page() { export default function Page() {
return ( return (
<> <Products
<Header title="Shopify Hydrogen Storefront"
storeName="Shop" subtitle="An agent-friendly Shopify storefront built with Next.js and Hydrogen."
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."
/>
</>
); );
} }
+11 -29
View File
@@ -1,6 +1,4 @@
import { notFound } from 'next/navigation'; 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'; import { getShopPolicy, POLICY_HANDLES } from '@/hooks/use-shopify-policies';
export function generateStaticParams() { export function generateStaticParams() {
@@ -33,33 +31,17 @@ export default async function PolicyPage({
} }
return ( return (
<> <main className="max-w-screen-2xl mx-auto w-full px-5 lg:px-10 py-16">
<Header <div className="max-w-2xl mx-auto">
storeName="Shop" <h1 className="text-3xl md:text-4xl font-normal text-foreground">
logoUrl="" {policy.title}
links={[ </h1>
{ 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
<div className="max-w-2xl mx-auto"> className="policy-body mt-8 text-[15px] leading-7 text-foreground"
<h1 className="text-3xl md:text-4xl font-normal text-foreground"> dangerouslySetInnerHTML={{ __html: policy.body }}
{policy.title} />
</h1> </div>
</main>
<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."
/>
</>
); );
} }
-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 ProductDetail from '@/components/shopify/product-detail';
import ProductRecommendations from '@/components/shopify/product-recommendations'; import ProductRecommendations from '@/components/shopify/product-recommendations';
export default function Page() { export default function Page() {
return ( return (
<> <>
<Header
storeName="Shop"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<ProductDetail addToCartLabel="Add to Cart" /> <ProductDetail addToCartLabel="Add to Cart" />
<ProductRecommendations title="You May Also Like" /> <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 { Suspense } from 'react';
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import SearchResults from '@/components/shopify/search-results'; import SearchResults from '@/components/shopify/search-results';
export const metadata = { export const metadata = {
@@ -9,25 +7,9 @@ export const metadata = {
export default function Page() { export default function Page() {
return ( return (
<> // useSearchParams needs a Suspense boundary to prerender this route.
<Header <Suspense fallback={<div className="py-10" />}>
storeName="Shop" <SearchResults />
logoUrl="" </Suspense>
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."
/>
</>
); );
} }
+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'; import Collections from '@/components/shopify/collections';
export default function Page() { export default function Page() {
return ( return <Collections title="Our Collections" />;
<>
<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."
/>
</>
);
} }
+12
View File
@@ -0,0 +1,12 @@
import type { NavLink } from '@/components/shopify/header';
/** Chrome shared by every route — rendered once in the root layout. */
export const site = {
storeName: 'Shop',
logoUrl: '',
copyright: '© 2026 Shop. All rights reserved.',
navLinks: [
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
] as NavLink[],
};