Skip to content

Commit

Permalink
refactor: wrap header and footer in suspense
Browse files Browse the repository at this point in the history
  • Loading branch information
deini committed Sep 19, 2024
1 parent 17692ca commit 4d67210
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-donuts-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

wraps header and footer in suspense boundaries
39 changes: 9 additions & 30 deletions core/app/[locale]/(default)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,31 @@
import { unstable_setRequestLocale } from 'next-intl/server';
import { PropsWithChildren } from 'react';
import { PropsWithChildren, Suspense } from 'react';

import { getSessionCustomerId } from '~/auth';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { Footer } from '~/components/footer/footer';
import { FooterFragment } from '~/components/footer/fragment';
import { Header } from '~/components/header';
import { Header, HeaderSkeleton } from '~/components/header';
import { Cart } from '~/components/header/cart';
import { HeaderFragment } from '~/components/header/fragment';
import { LocaleType } from '~/i18n/routing';

interface Props extends PropsWithChildren {
params: { locale: LocaleType };
}

const LayoutQuery = graphql(
`
query LayoutQuery {
site {
...HeaderFragment
...FooterFragment
}
}
`,
[HeaderFragment, FooterFragment],
);

export default async function DefaultLayout({ children, params: { locale } }: Props) {
export default function DefaultLayout({ children, params: { locale } }: Props) {
unstable_setRequestLocale(locale);

const customerId = await getSessionCustomerId();

const { data } = await client.fetch({
document: LayoutQuery,
fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } },
});

return (
<>
<Header cart={<Cart />} data={data.site} />
<Suspense fallback={<HeaderSkeleton />}>
<Header cart={<Cart />} />
</Suspense>

<main className="flex-1 px-4 2xl:container sm:px-10 lg:px-12 2xl:mx-auto 2xl:px-0">
{children}
</main>

<Footer data={data.site} />
<Suspense>
<Footer />
</Suspense>
</>
);
}
15 changes: 15 additions & 0 deletions core/app/[locale]/(default)/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { graphql } from '~/client/graphql';
import { FooterFragment } from '~/components/footer/fragment';
import { HeaderFragment } from '~/components/header/fragment';

export const LayoutQuery = graphql(
`
query LayoutQuery {
site {
...HeaderFragment
...FooterFragment
}
}
`,
[HeaderFragment, FooterFragment],
);
30 changes: 15 additions & 15 deletions core/app/[locale]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';
import { ShoppingCart } from 'lucide-react';
import { getTranslations } from 'next-intl/server';
import { Suspense } from 'react';

import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { Footer } from '~/components/footer/footer';
import { FooterFragment } from '~/components/footer/fragment';
import { Header } from '~/components/header';
import { Header, HeaderSkeleton } from '~/components/header';
import { CartLink } from '~/components/header/cart';
import { HeaderFragment } from '~/components/header/fragment';
import { ProductCardFragment } from '~/components/product-card/fragment';
import { ProductCardCarousel } from '~/components/product-card-carousel';
import { SearchForm } from '~/components/search-form';
Expand All @@ -18,8 +17,6 @@ const NotFoundQuery = graphql(
`
query NotFoundQuery {
site {
...HeaderFragment
...FooterFragment
featuredProducts(first: 4) {
edges {
node {
Expand All @@ -30,7 +27,7 @@ const NotFoundQuery = graphql(
}
}
`,
[HeaderFragment, FooterFragment, ProductCardFragment],
[ProductCardFragment],
);

export default async function NotFound() {
Expand All @@ -45,14 +42,15 @@ export default async function NotFound() {

return (
<>
<Header
cart={
<CartLink>
<ShoppingCart aria-label="cart" />
</CartLink>
}
data={data.site}
/>
<Suspense fallback={<HeaderSkeleton />}>
<Header
cart={
<CartLink>
<ShoppingCart aria-label="cart" />
</CartLink>
}
/>
</Suspense>

<main className="mx-auto mb-10 max-w-[835px] space-y-8 px-4 sm:px-10 lg:px-0">
<div className="flex flex-col gap-8 px-0 py-16">
Expand All @@ -68,7 +66,9 @@ export default async function NotFound() {
/>
</main>

<Footer data={data.site} />
<Suspense>
<Footer />
</Suspense>
</>
);
}
Expand Down
19 changes: 14 additions & 5 deletions core/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
} from '@icons-pack/react-simple-icons';
import { JSX } from 'react';

import { FragmentOf } from '~/client/graphql';
import { LayoutQuery } from '~/app/[locale]/(default)/query';
import { getSessionCustomerId } from '~/auth';
import { client } from '~/client';
import { readFragment } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { Footer as ComponentsFooter } from '~/components/ui/footer';
import { logoTransformer } from '~/data-transformers/logo-transformer';

Expand All @@ -31,11 +35,16 @@ const socialIcons: Record<string, { icon: JSX.Element }> = {
YouTube: { icon: <SiYoutube title="YouTube" /> },
};

interface Props {
data: FragmentOf<typeof FooterFragment>;
}
export const Footer = async () => {
const customerId = await getSessionCustomerId();

const { data: response } = await client.fetch({
document: LayoutQuery,
fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } },
});

const data = readFragment(FooterFragment, response).site;

export const Footer = ({ data }: Props) => {
const sections = [
{
title: 'Categories',
Expand Down
40 changes: 36 additions & 4 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { ShoppingCart, User } from 'lucide-react';
import { getLocale, getTranslations } from 'next-intl/server';
import { ReactNode, Suspense } from 'react';

import { LayoutQuery } from '~/app/[locale]/(default)/query';
import { getSessionCustomerId } from '~/auth';
import { FragmentOf } from '~/client/graphql';
import { client } from '~/client';
import { readFragment } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { logoTransformer } from '~/data-transformers/logo-transformer';
import { localeLanguageRegionMap } from '~/i18n/routing';

Expand All @@ -19,15 +22,20 @@ import { QuickSearch } from './quick-search';

interface Props {
cart: ReactNode;
data: FragmentOf<typeof HeaderFragment>;
}

export const Header = async ({ cart, data }: Props) => {
export const Header = async ({ cart }: Props) => {
const locale = await getLocale();
const t = await getTranslations('Components.Header');

const customerId = await getSessionCustomerId();

const { data: response } = await client.fetch({
document: LayoutQuery,
fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } },
});

const data = readFragment(HeaderFragment, response).site;

/** To prevent the navigation menu from overflowing, we limit the number of categories to 6.
To show a full list of categories, modify the `slice` method to remove the limit.
Will require modification of navigation menu styles to accommodate the additional categories.
Expand Down Expand Up @@ -97,3 +105,27 @@ export const Header = async ({ cart, data }: Props) => {
/>
);
};

export const HeaderSkeleton = () => (
<header className="flex min-h-[92px] animate-pulse items-center justify-between gap-1 overflow-y-visible bg-white px-4 2xl:container sm:px-10 lg:gap-8 lg:px-12 2xl:mx-auto 2xl:px-0">
<div className="h-16 w-40 rounded bg-slate-200" />
<div className="hidden space-x-4 lg:flex">
<div className="h-6 w-20 rounded bg-slate-200" />
<div className="h-6 w-20 rounded bg-slate-200" />
<div className="h-6 w-20 rounded bg-slate-200" />
<div className="h-6 w-20 rounded bg-slate-200" />
</div>
<div className="flex items-center gap-2 lg:gap-4">
<div className="h-8 w-8 rounded-full bg-slate-200" />

<div className="flex gap-2 lg:gap-4">
<div className="h-8 w-8 rounded-full bg-slate-200" />
<div className="h-8 w-8 rounded-full bg-slate-200" />
</div>

<div className="h-8 w-20 rounded bg-slate-200" />

<div className="h-8 w-8 rounded bg-slate-200 lg:hidden" />
</div>
</header>
);

0 comments on commit 4d67210

Please sign in to comment.