Build a Sitecore Next.js component using GraphQL connected mode
Connected GraphQL in Sitecore executes GraphQL queries directly against the Sitecore GraphQL endpoint
. In contrast to the integrated GraphQL, this is an additional call -
one to the Layout Service
(to get data about datasource/page item data) and one to the Sitecore GraphQL endpoint for your component data.
In this guide, we will learn how to build a Sitecore Next.js component using GraphQL in connected mode. We will build a main navigation so that it pulls data from Sitecore.
If you haven't already, make sure you've created the navigation items. Also, see how we've tested and build our GraphQL query.
Our navigation items has the following structure:
And our GraphQL query (note we've changed the query name):
query MainNavigationConnectedQuery($datasource: String!, $language: String!) {
datasource: item(path: $datasource, language: $language) {
id
name
children {
results {
id
name
... on NavigationItem {
navigationLink {
jsonValue
}
}
}
}
}
}
Create Navigation Json rendering
In the Content Editor,
- Navigate to /sitecore/layout/Renderings/Feature and create a
Navigation
rendering folder if it hasn't been created yet - Under the
Navigation
folder, create a Json renderingMainNavigationConnected
- On the
MainNavigationConnected
item, set fields- Datasource Location:
/sitecore/content/XmNextJs/Navigation
- Datasource Template:
/sitecore/templates/Feature/Navigation/Navigation Folder
- Datasource Location:
In contrast to the Integrated mode, we do not enter the GraphQL into Component GraphQL Query
here.
Layout clean up
Skip to placeholder updates, if you did this previously in the Integrated mode excercise.
If you did not do the layout cleanup from the Integrated mode excercise, we'll have to update the src\rendering\src\Layout.tsx
.
Notice that the Navigation
component is outside of the jss-main
placeholder. We'll need to clean this up and add a placeholder to hold our new MainNavigationComponent
rendering.
return (
<>
<Scripts />
<Head>
<title>{fields.pageTitle.value.toString() || 'Page'}</title>
<link rel="icon" href={`${publicUrl}/favicon.ico`} />
</Head>
<Navigation />
{/* root placeholder for the app, which we add components to using route data */}
<div className="container">{route && <Placeholder name="jss-main" rendering={route} />}</div>
</>
);
- Remove the
Navigation
component - Add a
jss-header
placeholder - While we're here, let's make our HTML more semantic
- Wrap the
jss-header
placeholder with<header>
- Change
div.container
tomain.container
- Wrap the
return (
<>
<Scripts />
<Head>
<title>{fields.pageTitle.value.toString() || 'Page'}</title>
<link rel="icon" href={`${publicUrl}/favicon.ico`} />
</Head>
<header>
{route && <Placeholder name="jss-header" rendering={route} />}
</header>
{/* root placeholder for the app, which we add components to using route data */}
<main className="container">{route && <Placeholder name="jss-main" rendering={route} />}</main>
</>
);
Placeholder updates
We previously learned the steps necessary to add a placeholder - we'll have to do the same steps here for our jss-header
placeholder.
In the Content Editor,
- Update the
jss-header
placeholder setting to allow theMainNavigationConnected
rendering.- Navigate to
/sitecore/layout/Placeholder Settings/Project/xmnextjs/jss-header
- Under the
Allowed Controls
, addMainNavigationConnected
- Navigate to
- Update the
xmnextjs Layout
to renderjss-header
data. This was previously done on the integrated mode excercise.- Navigate to
/sitecore/layout/Layouts/Project/xmnextjs/xmnextjs Layout
- Under
Layout Service Placeholders
field, addjss-header
- Navigate to
Add rendering
We would like to add the MainNavigationConnected
rendering to every page. It is generally a good idea to create a base page template and have other page
templates inherit from this template. Fortunately for us, in our create a page template tutorial,
we decided to use the App Route
as our base template (Article Route inherits from this template). We'll continue to do the same.
- Navigate to
/sitecore/templates/Project/xmnextjs/App Route/__Standard Values
. Create the Standard Values, if it wasn't already created. - Under
Presentation > Details > Shared Layout
, change theMainNavigationIntegrated
rendering to theMainNavigationConnected
rendering, keeping the same placeholder and data source.- Placeholder:
jss-header
- Data Source:
/sitecore/content/XmNextJs/Navigation/Header
- Placeholder:
In Sitecore, publish our changes.
Layout Service
Let's check our Layout Service
and see what gets returned.
In a browser, open https://cm.xmnextjs.localhost/sitecore/api/layout/render/jss?item=/&sc_apikey={YOUR API KEY}&sc_mode=normal
{
"sitecore": {
...
"route": {
"placeholders": {
...
"jss-header": [
{
"uid": "b1e781a3-a93a-42e5-8ae5-fb3feefbf6a9",
"componentName": "MainNavigationConnected",
"dataSource": "{2CD2AB0A-034B-474A-891B-87F8B883C0A8}",
"params": {},
"fields": {}
}
]
}
}
}
}
In contrast to the integrated mode, the layout service does not contain your GraphQL data.
GraphQL Types Update
Since GraphQL is strongly typed, we'll need to let the Next.js application know about the Navigation Item
template we've created.
Publish Sitecore templates
Publish the Sitecore templates so that they are available on the web database
Update introspection data
On src/rendering
directory execute the command:
jss graphql:update
This will update src\rendering\src\temp\GraphQLIntrospectionResult.json
with data about the Navigation Item
.
Create GraphQL
Create the GraphQL file
Under the src\components\Feature\Navigation
folder create a MainNavigationConnectedQuery.graphql
file and paste in the query we created.
query MainNavigationConnectedQuery($datasource: String!, $language: String!) {
datasource: item(path: $datasource, language: $language) {
id
name
children {
results {
id
name
... on NavigationItem {
navigationLink {
jsonValue
}
}
}
}
}
}
Generate types
We'll need to regenerate the types so that our tsx files are aware of the NavigationItem
template. On src/rendering
execute:
npm run bootstrap
You should see a MainNavigationConnectedQuery.graphql.d.ts
type file generated on same directory as your GraphQL file.
Alternatively, restarting your rendering host
application will also generate the types.
Create component
We are now ready to create our Main Navigation component using GraphQL in connected mode.
Create a type
Create a type that corresponds to our expect GraphQL return.
import {
NavigationItem,
} from './MainNavigationConnectedQuery.graphql';
type NavigationData = {
id: string,
name: string,
children: {
results: Array<NavigationItem>
}
}
Add HTML
Add some HTML. Note that we're not rendering the navigation items just yet.
import {
NavigationItem,
} from './MainNavigationConnectedQuery.graphql';
type NavigationData = {
id: string,
name: string,
children: {
results: Array<NavigationItem>
}
}
const MainNavigationConnected = (): JSX.Element => {
return (
<div className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom">
<h5 className="my-0 mr-md-auto font-weight-normal">
<a className="text-dark" href="/">
<img src={`/sc_logo.svg`} alt="Sitecore" />
</a>
</h5>
<nav className="my-2 my-md-0 mr-md-3">
{/* nav here */}
</nav>
</div>
);
};
export default MainNavigationConnected;
Use GraphQL to get data
import {
GetStaticComponentProps,
GraphQLRequestClient,
} from '@sitecore-jss/sitecore-jss-nextjs';
import {
MainNavigationConnectedQueryDocument,
NavigationItem,
} from './MainNavigationConnectedQuery.graphql';
type NavigationData = {
id: string,
name: string,
children: {
results: Array<NavigationItem>
}
}
const MainNavigationConnected = (): JSX.Element => {
return (
<div className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom">
<h5 className="my-0 mr-md-auto font-weight-normal">
<a className="text-dark" href="/">
<img src={`/sc_logo.svg`} alt="Sitecore" />
</a>
</h5>
<nav className="my-2 my-md-0 mr-md-3">
{/* nav here */}
</nav>
</div>
);
};
/**
* Will be called during SSG
* @param {ComponentRendering} rendering
* @param {LayoutServiceData} layoutData
* @param {GetStaticPropsContext} context
*/
export const getStaticProps: GetStaticComponentProps = async (rendering, layoutData) => {
const graphQLClient = new GraphQLRequestClient(config.graphQLEndpoint, {
apiKey: config.sitecoreApiKey,
});
const result = await graphQLClient.request<NavigationData>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MainNavigationConnectedQueryDocument as any,
{
datasource: rendering.dataSource,
language: layoutData?.sitecore?.context?.language,
}
);
return result;
};
export default MainNavigationConnected;
For Server Side Rendering, replace getStaticProps
with getServerSideProps
.
import {
getServerSideProps,
} from '@sitecore-jss/sitecore-jss-nextjs';
export const getServerSideProps: GetServerSideComponentProps = async (rendering, layoutData) => {
const graphQLClient = new GraphQLRequestClient(config.graphQLEndpoint, {
apiKey: config.sitecoreApiKey,
});
const result = await graphQLClient.request<NavigationData>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MainNavigationConnectedQueryDocument as any,
{
datasource: rendering.dataSource,
language: layoutData?.sitecore?.context?.language,
}
);
return result;
};
The MainNavigationConnectedQueryDocument
type can be found the under the generated type file. The MainNavigationConnectedQueryDocument
type contains properties to pass to the GraphQL query.
export declare const MainNavigationConnectedQueryDocument: DocumentNode<MainNavigationConnectedQueryQuery, Exact<{
datasource: Scalars['String'];
language: Scalars['String'];
}>>;
Confirm data
Visit your home page and confirm the GraphQL call _next/data/development/en.json
Map and render data
Once we've confirmed that the data returns, let's map and render our data. We'll do some clean up as well.
import { Link } from '@sitecore-jss/sitecore-jss-nextjs'
import {
GetStaticComponentProps,
constants,
GraphQLRequestClient,
withDatasourceCheck,
} from '@sitecore-jss/sitecore-jss-nextjs';
import {
MainNavigationConnectedQueryDocument,
NavigationItem,
} from './MainNavigationConnectedQuery.graphql';
import config from 'temp/config';
import { ComponentProps } from 'lib/component-props';
type NavigationData = {
id: string,
name: string,
children: {
results: Array<NavigationItem>
}
}
type MainNavigationProps = ComponentProps & {
datasource: NavigationData
}
const MainNavigationConnected =(props: MainNavigationProps): JSX.Element => {
// loop through the datasource children and grab all the jsonValues
let navigationItems = props.datasource?.children.results.map(i => i.navigationLink?.jsonValue);
return (
<div className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom">
<h5 className="my-0 mr-md-auto font-weight-normal">
<a className="text-dark" href="/">
<img src={`/sc_logo.svg`} alt="Sitecore" />
</a>
</h5>
<nav className="my-2 my-md-0 mr-md-3">
{/*
The Link component expects an object on the field property with the structure
"value": {
"href": "...",
"text": "...",
"linktype": "...",
"url": "...",
"anchor": "...",
"target": "..."
}
*/}
{ navigationItems && navigationItems.map((item, index) => (
<Link
key={index}
field={item}
className='p-2 text-dark'
/>
))
}
</nav>
</div>
);
};
/**
* Will be called during SSG
* @param {ComponentRendering} rendering
* @param {LayoutServiceData} layoutData
* @param {GetStaticPropsContext} context
*/
export const getStaticProps: GetStaticComponentProps = async (rendering, layoutData) => {
if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) {
return null;
}
const graphQLClient = new GraphQLRequestClient(config.graphQLEndpoint, {
apiKey: config.sitecoreApiKey,
});
const result = await graphQLClient.request<NavigationData>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MainNavigationConnectedQueryDocument as any,
{
datasource: rendering.dataSource,
language: layoutData?.sitecore?.context?.language,
}
);
return result;
};
export default withDatasourceCheck()<ComponentProps>(MainNavigationConnected);
Knowledge check:
- What is the connected GraphQL mode in Sitecore?
- Where do you enter the GraphQL query in connected mode?
- In connected mode, does the GraphQL data come from the Layout Service? If not, where does it come from?
- How do you regenerate the GraphQL introspection data?
- How do you regenerate types that corresponds to Sitecore templates?