Build a Sitecore Next.js component using GraphQL integrated mode
Integrated GraphQL in Sitecore executes GraphQL queries within the Layout Service endpoint, and merges the query results into the Layout Service result JSON.
This means that we only need to make one call to the Layout Service to grab both the datasource/page item data as well as what we queried with GraphQL.

In this guide, we will learn how to build a Sitecore Next.js component using GraphQL in integrated mode. So far our current main navigation is hardcoded - we will rebuild it so that each link is content editable. Also, since navigations are typically rendered in a different area of the page, we will utilize our previous knowledge of placeholders and add a header placeholder.
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:
query MainNavigationIntegratedQuery($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/Featureand create aNavigationrendering folder - Under the
Navigationfolder, create a Json renderingMainNavigationIntegrated - On the
MainNavigationIntegrateditem, set fields- Datasource Location:
/sitecore/content/XmNextJs/Navigation - Datasource Template:
/sitecore/templates/Feature/Navigation/Navigation Folder - Component GraphQL Query: paste in the query above
- Datasource Location:

Layout clean up
If we examine 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 MainNavigationIntegrated 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
Navigationcomponent - Add a
jss-headerplaceholder - While we're here, let's make our HTML more semantic
- Wrap the
jss-headerplaceholder with<header> - Change
div.containertomain.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-headerplaceholder setting to allow theMainNavigationIntegratedrendering- Navigate to
/sitecore/layout/Placeholder Settings/Project/xmnextjs/jss-header - Under the
Allowed Controls, addMainNavigationIntegrated
- Navigate to
- Update the
xmnextjs Layoutto renderjss-headerdata- Navigate to
/sitecore/layout/Layouts/Project/xmnextjs/xmnextjs Layout - Under
Layout Service Placeholdersfield, addjss-header
- Navigate to
Add rendering
We would like to add the MainNavigationIntegrated 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, add theMainNavigationIntegratedrendering- Placeholder:
jss-header - Data Source:
/sitecore/content/XmNextJs/Navigation/Header
- Placeholder:
In Sitecore, publish our changes.
Layout Service
As always, lets double check that our Layout Service returns the expected data.
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-main": [...],
"jss-header": [
{
"uid": "420108ad-deda-4944-a09d-63fdb9811582",
"componentName": "MainNavigationIntegrated",
"dataSource": "{2CD2AB0A-034B-474A-891B-87F8B883C0A8}",
"params": {},
"fields": {
"data": {
"datasource": {
"id": "2CD2AB0A034B474A891B87F8B883C0A8",
"name": "Header",
"children": {
"results": [
{
"id": "9F7596596F4C4EB48DF69F178260AB69",
"name": "Documentation",
"navigationLink": {
"jsonValue": {
"value": {
"href": "https://jss.sitecore.com",
"text": "Documentation",
"linktype": "external",
"url": "https://jss.sitecore.com",
"anchor": "",
"target": "_blank"
}
}
}
},
...Take a note of the data structure returned by our GraphQL query in the Layout Service. We will need to map these on our component.
Navigation Component
Finally, it is time to create our MainNavigationIntegrated component. We need to map our GraphQL data to types, loop through the data, and
render our links. Note the usage of the Link component provided by '@sitecore-jss/sitecore-jss-nextjs'.
import { Link, LinkField, withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs'
import { getPublicUrl } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';
// Prefix public assets with a public URL to enable compatibility with Sitecore Experience Editor.
// If you're not supporting the Experience Editor, you can remove this.
const publicUrl = getPublicUrl();
// These types maps to the Layout Service which has the GraphQL data
type MainNavigationProps = ComponentProps & {
fields: {
data: {
datasource: DataSource
}
}
}
type DataSource = {
id: string,
name: string,
children: {
results: Array<NavigationItem>
}
}
type NavigationItem = {
id: string,
name: string,
navigationLink: {
jsonValue: LinkField
}
}
const MainNavigationIntegrated = (props: MainNavigationProps): JSX.Element => {
// loop through the datasource children and grab all the jsonValues
let navigationItems = props.fields.data.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={`${publicUrl}/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>
);
};
export default withDatasourceCheck()<MainNavigationProps>(MainNavigationIntegrated);Knowledge check:
- What is the integrated GraphQL mode in Sitecore?
- Where do you enter the GraphQL query?
- Where does the GraphQL data come from in Integrated mode?
- Excercise - there are actually unused fields in our GraphQL query. Remove the
idandnamefields from the query as we wish our payload to be as light as possible.