Previously, you created a custom storefront and started customizing your storefront in GraphCommerce. You're now ready to build pages in your GraphCommerce app.
In this tutorial, you'll accomplish a series of tasks to add some specific functionality to your custom storefront. The result will be simple, but you'll learn where to find resources to build more complex features on your own.
When you only want to create a new content page for a specific URL based on Hygraph rows, you don't need to create a custom template: After creating a page in Hygraph it will automatically be handled by the
/pages/[...url].tsx
route.
You've familiarized yourself with React ↗, Next.js ↗, and Mui ↗. GraphCommerce is a frontend React framework that uses Next.js for server-side rendering.
export default function AboutUs() {
return <>About Us</>
}
import { PageOptions } from '@graphcommerce/framer-next-pages'
import { StoreConfigDocument } from '@graphcommerce/magento-store'
import {
GetStaticProps,
LayoutOverlayHeader,
LayoutTitle,
PageMeta,
} from '@graphcommerce/next-ui'
import {
LayoutDocument,
LayoutNavigation,
LayoutNavigationProps,
} from '../../components'
import {
graphqlSsrClient,
graphqlSharedClient,
} from '../../lib/graphql/graphqlSsrClient'
type GetPageStaticProps = GetStaticProps<LayoutNavigationProps>
function AboutUs() {
return (
<>
<LayoutOverlayHeader>
<LayoutTitle size='small' component='span'>
About us
</LayoutTitle>
</LayoutOverlayHeader>
<PageMeta title='About us' />
<LayoutTitle>About us</LayoutTitle>
</>
)
}
const pageOptions: PageOptions<LayoutNavigationProps> = {
Layout: LayoutNavigation,
}
AboutUs.pageOptions = pageOptions
export default AboutUs
export const getStaticProps: GetPageStaticProps = async ({ locale }) => {
const client = graphqlSharedClient(locale)
const staticClient = graphqlSsrClient(locale)
const conf = client.query({ query: StoreConfigDocument })
const layout = staticClient.query({
query: LayoutDocument,
fetchPolicy: 'cache-first',
})
return {
props: {
...(await layout).data,
apolloState: await conf.then(() => client.cache.extract()),
},
revalidate: 60 * 20,
}
}
# Example from /components/Layout/Layout.graphql (LayoutDocument)
query Layout {
menu: categories {
__typename
}
...MenuQueryFragment
...FooterQueryFragment
}
In the getStaticProps
function, the query StoreConfigDocument
is used to
fetch information about the Magento storeview. Then, the query LayoutDocument
is used to fetch the data required to render the menu, footer and page content.
The function getStaticProps
is used to fetch data, meaning content is rendered
on the server. Review the page's source code and search for About Us
to
validate that this string (currently hard-coded) is part of the source code.
import { PageOptions } from '@graphcommerce/framer-next-pages'
import {
hygraphPageContent,
HygraphPagesQuery,
} from '@graphcommerce/graphcms-ui'
import { StoreConfigDocument } from '@graphcommerce/magento-store'
import {
GetStaticProps,
LayoutOverlayHeader,
LayoutTitle,
PageMeta,
} from '@graphcommerce/next-ui'
import { GetStaticPaths } from 'next'
import {
LayoutDocument,
LayoutNavigation,
LayoutNavigationProps,
RowRenderer,
} from '../../components'
import {
graphqlSsrClient,
graphqlSharedClient,
} from '../../lib/graphql/graphqlSsrClient'
type Props = HygraphPagesQuery
type RouteProps = { url: string }
type GetPageStaticPaths = GetStaticPaths<RouteProps>
type GetPageStaticProps = GetStaticProps<
LayoutNavigationProps,
Props,
RouteProps
>
function AboutUs(props: Props) {
const { pages } = props
const page = pages[0]
return (
<>
<LayoutOverlayHeader>
<LayoutTitle size='small' component='span'>
{page.title}
</LayoutTitle>
</LayoutOverlayHeader>
<PageMeta
title={page.metaTitle ?? ''}
metaDescription={page.metaDescription}
/>
<LayoutTitle>{page.title}</LayoutTitle>
<RowRenderer content={page.content} />
</>
)
}
const pageOptions: PageOptions<LayoutNavigationProps> = {
Layout: LayoutNavigation,
}
AboutUs.pageOptions = pageOptions
export default AboutUs
export const getStaticProps: GetPageStaticProps = async ({ locale }) => {
const client = graphqlSharedClient(locale)
const staticClient = graphqlSsrClient(locale)
const conf = client.query({ query: StoreConfigDocument })
const page = hygraphPageContent(staticClient, 'about/about-us')
const layout = staticClient.query({
query: LayoutDocument,
fetchPolicy: 'cache-first',
})
if (!(await page).data.pages?.[0]) return { notFound: true }
return {
props: {
...(await page).data,
...(await layout).data,
apolloState: await conf.then(() => client.cache.extract()),
},
revalidate: 60 * 20,
}
}
/about/about-us.tsx
to /about/[url].tsx
export const getStaticPaths: GetPageStaticPaths = (context) => ({
paths: [],
fallback: 'blocking',
})
export const getStaticProps: GetPageStaticProps = async ({
locale,
params,
}) => {
const client = graphqlSharedClient(locale)
const staticClient = graphqlSsrClient(locale)
const conf = client.query({ query: StoreConfigDocument })
const page = hygraphPageContent(staticClient, `about/${params?.url}`)
const layout = staticClient.query({
query: LayoutDocument,
fetchPolicy: 'cache-first',
})
if (!(await page).data.pages?.[0]) return { notFound: true }
return {
props: {
...(await page).data,
...(await layout).data,
apolloState: await conf.then(() => client.cache.extract()),
},
revalidate: 60 * 20,
}
}
By renaming the file to /about/[url].tsx
, all routes starting with /about/
will be handled by the file (dynamic routing). Pages that have dynamic routes
need a list of paths to be statically generated.
All paths specified by a function called getStaticPaths
will be statically
pre-rendered at build-time.
In the example above, the array with paths is empty. The required getStaticPaths
function is there, but no URLs are pre-rendered. Because getStaticPaths has the
option fallback: 'blocking'
, the paths that have not been pre-rendered at
built-time will not result in a 404:
From the getStaticPaths API reference ↗: If fallback is 'blocking', new paths not returned by getStaticPaths will wait for the HTML to be generated, identical to SSR (hence why blocking), and then be cached for future requests so it only happens once per path.
export const getStaticPaths: GetPageStaticPaths = async ({ locales = [] }) => {
if (process.env.NODE_ENV === 'development')
return { paths: [], fallback: 'blocking' }
const path = async (locale: string) => {
const client = graphqlSsrClient(locale)
const { data } = await client.query({
query: PagesStaticPathsDocument,
variables: {
first: import.meta.graphCommerce.limitSsg ? 1 : 1000,
urlStartsWith: 'about',
},
})
return data.pages.map((page) => ({
params: { url: page.url.split('/').slice(1) },
locale,
}))
}
const paths = (await Promise.all(locales.map(path))).flat(1)
return { paths, fallback: 'blocking' }
}
The PagesStaticPathsDocument query is used to fetch all pages from Hygraph that have a URL starting with 'about'. The locale options from the context object are used to create an array:
// Terminal output for console.log(paths), upon refreshing the page
[
{ params: { url: 'about-us' }, locale: 'en-us' },
{ params: { url: 'about-us' }, locale: 'nl' },
{ params: { url: 'about-us' }, locale: 'fr-be' },
{ params: { url: 'about-us' }, locale: 'nl-be' },
{ params: { url: 'about-us' }, locale: 'en-gb' },
{ params: { url: 'about-us' }, locale: 'en-ca' },
]
You can test the static build process by running it locally:
cd /examples/magento-graphcms/
Navigate to the project directoryyarn build
Start static build process