こんにちは、Sitecore XM Cloud 技術担当の永田です。
今回は、Sitecore XM CloudでGraphQL クエリをコード側で実行するコンポーネントの作成手順をご紹介します。
本記事ではGraphQL クエリをコード側で実行する = フロントエンドアプリ内に GraphQL クエリを記述したファイルを配置し、コンポーネントのコードからクエリを実行してデータを取得することとしています。
公式ドキュメントでは「Use GraphQL to fetch component-level data in JSS Next.js apps」と呼ばれています。
完成イメージはこちらです。
ニュースページとして作成したアイテムの概要やサムネイル等を、GraphQL で日付の順に取得して表示しています。

実装したコードと Sitecore アイテムは Github に置いています!
以下のリンクからご覧ください。
Recent コンポーネント ドキュメント
GraphQL クエリをコード側で実行する理由は以下のようなものがあります。
- クエリの動的な組み立て
- 例えば検索処理の場合、検索ワードやソートなどのユーザーが指定する部分があるため、コードにより動的にクエリを組み立てる必要があります
- ISR と組み合わせることにより最新のデータを取得可能
- 例えば記事ページを取得して表示する記事一覧コンポーネントでは、記事ページのパブリッシュ時に記事一覧コンポーネントも(配置したページはパブリッシュせずに)更新したくなります
- コンポーネントの tsx ファイルの中にクエリを実行するコードを書いておくことで、ISR によりページを再生成する度に最新のデータを取得できます
なお、この記事は SXA の考え方に基づいています。
そのため、サイトツリーの構造など非 SXA のものと異なる箇所があります。
詳しくは以下のドキュメントをご覧ください。
Using SXA for XM Cloud development | Sitecore Documentation
また、イーストではXM Cloud用に作成した独自のコンポーネントをGitHubで公開中です!
ページ上部か下部のeast-xmcloud-extensionsのバナーからご覧ください。
作成手順
例として TOP ページに置くような、記事ページの最新のものをいくつか取得して表示するコンポーネントを作成します。
データソースとなるアイテムのテンプレートの作成
コンポーネントに表示するデータを定義するためのテンプレートを作成します。
- /sitecore/templates/Project/{あなたのサイト} 以下に任意の名前のテンプレートを作成します(例 : Recent)
- Base Template はTemplates/System/Templates/Standard templateを選択します
- components フォルダや datasources フォルダ等、わかりやすい名前でフォルダ分けしておくことをお勧めします
- Content タブを開き、Base template フィールドで /sitecore/templates/Foundation/Experience Accelerator/StandardValues/_PerSiteStandardValuesテンプレートを継承します
- サイトごとにスタンダードバリューを設定するためのテンプレートです
- Builder タブを開き、必要なフィールドを定義します
- Root(必須)
- Type:Droptree
- Source:query:$site/Home
- 取得対象の記事ページの親アイテムを指定するためのフィールドです
- RecentHeader(任意)
- Type:Rich Text 等
- コンポーネントの見出しを入力するためのフィールドです
- ListLink(任意)
- Type:General Link
- ニュースの一覧ページ等に遷移するリンクを入力するためのフィールドです
- その他設定したいフィールドがあれば追加してください
- 各フィールドの表示名や Title は任意に変更してください
- Root(必須)
完成イメージはこちらです。


フォルダテンプレートの作成
データソースのアイテムを格納するフォルダのテンプレートを作成します。
サイトごとの Data フォルダにこのフォルダを配置することで、その中にデータソースのアイテムを生成できます。
- /sitecore/templates/Project/{あなたのサイト} 以下に任意の名前のテンプレートを作成します(例 : Recent Folder)
- 特に変更が無ければ、/sitecore/templates/Feature/JSS Experience Accelerator/Page Content/Text Folder等の既存のテンプレートを複製することも可能です
- Base Template はTemplates/System/Templates/Standard templateを選択します
- detasourceFolder フォルダ等、わかりやすい名前でフォルダ分けしておくことをお勧めします
- テンプレートのスタンダードバリューを作成し、挿入オプションにデータソースのアイテムのテンプレートを追加します
- フォルダを入れ子構造にする場合はこのフォルダのテンプレートも追加します
完成イメージはこちらです。


レンダリングパラメータテンプレートの作成
コンポーネントのスタイル等のパラメータを設定するためのテンプレートを作成します。
- /sitecore/templates/Project/{あなたのサイト} 以下に任意の名前のテンプレートを作成します(例 : Recent)
- 特に変更が無ければ、/sitecore/templates/Feature/JSS Experience Accelerator/Page Content/Rendering Parameters/Rich Text等の既存のテンプレートを複製することも可能です
- Base Template はTemplates/Foundation/JSS Experience Accelerator/Presentation/Rendering Parameters/BaseRenderingParametersを選択します
- RenderingParameters フォルダ等、わかりやすい名前でフォルダ分けしておくことをお勧めします
- Content タブを開き、Base template フィールドで以下のテンプレートを継承します
- /sitecore/templates/Foundation/JSS Experience Accelerator/Presentation/Rendering Parameters/BaseRenderingParameters(上記で継承済)
- /sitecore/templates/Foundation/Experience Accelerator/Dynamic Placeholders/Rendering Parameters/IDynamicPlaceholder
- /sitecore/templates/Foundation/Experience Accelerator/StandardValues/_PerSiteStandardValues
- /sitecore/templates/Foundation/Experience Accelerator/Markup Decorator/Rendering Parameters/IRenderingId
- Builder タブを開き、パラメータとして設定するデータをフィールドとして定義します
- PageDisplayCount(必須)
- Type:Integer
- 取得する記事の数を指定するためのフィールドです
- その他設定したいフィールドがあれば追加してください
- 各フィールドの表示名や Title は任意に変更してください
- PageDisplayCount(必須)
完成イメージはこちらです。


レンダリングアイテムの作成
ページに配置するための Sitecore アイテムを作成します。
- /sitecore/layout/Renderings/Project/{あなたのサイト} 以下に Json Rendering アイテムを作成します(例 : Recent)
- アイテム名は任意ですが、この後作成する tsx ファイルの名前と揃えることをお勧めします
- 揃えなかった場合、Component Name フィールドに tsx ファイルの名前を入力してください
- Other properties フィールドに以下のプロパティを追加します
- IsRenderingsWithDynamicPlaceholders:true
- Parameters Template フィールドに作成したレンダリングパラメータテンプレートを設定します
- Datasource Location フィールドにデータソースのアイテムを格納する場所やクエリを設定します
- 例:
query:$site/*[@@name='Data']/*[@@templatename='Recent Folder']|query:$sharedSites/*[@@name='Data']/*[@@templatename='Recent Folder']
- 例:
- Datasource Template フィールドにデータソースのアイテムのテンプレートを設定します
- アイテム名は任意ですが、この後作成する tsx ファイルの名前と揃えることをお勧めします
完成イメージはこちらです。




フォルダ等その他必要なアイテムの作成
その他必要なアイテムを作成します。
- {あなたのサイト}/Data に作成したフォルダテンプレートを使って、データソースのフォルダを作成します
- {あなたのサイト}/Presentation/Available Renderings に任意の名前で Available Renderings を追加します(MyComponents 等)
- 上記の Available Renderings の Rendrings フィールドに作成したコンポーネントを追加します
- {あなたのサイト}/Presentation/Headless Variants にコンポーネントの名前の Variants を追加します
- 上記の子アイテムに Default という名前の Variant を作成します
GraphQL クエリ用 ファイルの作成
以下は「指定したアイテム配下のレイアウトを持つページ」を取得するクエリ例です。
フロントエンドアプリの \src{あなたのプロジェクト}\src\graphql フォルダに、ts ファイル(例:RecentQuery.dynamic.graphql.ts)を作成します。
ページから取得するフィールドは任意に変更してください。
export const RecentQuery = `query getRecent ($rootId: String!, $language: String!, $pageDisplayCount: Int!) {
search(
where: {
AND: [
{
name: "_language",
value: $language,
operator: EQ
},
{
name: "_hasLayout",
value: "true",
operator: EQ
},
{
name: "_parent",
value: $rootId,
operator: CONTAINS
}
]
}
orderBy: {
name: "ReleaseDate"
direction: DESC
}
first: $pageDisplayCount
) {
results {
id
Title: field(name: "Title") {
jsonValue
}
Thumbnail: field(name: "Thumbnail") {
...on ImageField {
jsonValue
}
}
Overview: field(name: "Overview") {
jsonValue
}
ReleaseDate: field(name: "ReleaseDate") {
...on DateField {
jsonValue
}
}
url {
path
}
}
}
}`;
tsx ファイルの作成
続いて、GraphQL クエリを実行するためのコンポーネントのコード例です。
フロントエンドアプリの \src{あなたのプロジェクト}\src\components フォルダに、コンポーネントの tsx ファイル(例:Recent.tsx)を作成します。
ファイル名はレンダリングアイテムの Component Name フィールドとそろえてください。
getStaticProps / getServerSideProps
の中にクエリを実行するコードを書いておくことで、ページを再生成する度に最新のデータを取得できます。
なお、こちらのコードは JSS ver22.3.1 時点のものです。
また、データが無かった場合の例外処理などは含まれていないため、ご自身のプロジェクトの要件に合わせて追加してください。
import {
useSitecoreContext,
Text,
TextField,
Link as JssLink,
LinkField,
Image as JssImage,
ImageField,
RichText,
RichTextField,
DateField,
Item,
GetStaticComponentProps,
} from "@sitecore-jss/sitecore-jss-nextjs";
import { RecentQuery } from "src/graphql/RecentQuery.dynamic.graphql";
import graphqlClientFactory from "lib/graphql-client-factory";
import config from "temp/config";
import Link from "next/link";
interface RcentItemFields {
Title: { jsonValue: TextField };
Thumbnail: { jsonValue: ImageField };
Overview: { jsonValue: RichTextField };
ReleaseDate: { jsonValue: { value?: string; editable?: string } };
}
type RecentItemProps = {
id: string;
fields: RcentItemFields;
url: string;
};
interface Fields {
RecentHeader: RichTextField;
ListLink: LinkField;
}
interface RecentProps {
params: { [key: string]: string };
fields: Fields;
articles: RecentItemProps[];
}
interface RawItemData {
id: string;
Title: { jsonValue: TextField };
Thumbnail: { jsonValue: ImageField };
Overview: { jsonValue: RichTextField };
ReleaseDate: { jsonValue: { value?: string; editable?: string } };
url: {
path: string;
};
}
type SearchQueryData = {
search: {
results: RawItemData[];
};
};
type ComponentContentProps = {
pageState: string | undefined;
url: string;
children: JSX.Element;
};
const RecentItem = (props: ComponentContentProps): JSX.Element => {
return (
<li className="item">
{props.pageState !== "edit" ? (
<Link href={props.url}>{props.children}</Link>
) : (
<>{props.children}</>
)}
</li>
);
};
export const Default = (props: RecentProps): JSX.Element => {
const { sitecoreContext } = useSitecoreContext();
const id = props.params.RenderingIdentifier;
return (
<div
className={`component recent ${props.params.styles}`}
id={id ? id : undefined}
>
<div className="component-content">
<div className="recent-header">
<RichText field={props.fields.RecentHeader} />
</div>
<ul>
{props.articles &&
Array.isArray(props.articles) &&
props.articles.map((item) => (
<RecentItem
key={item.id}
pageState={sitecoreContext.pageState}
url={item.url}
>
<>
<div className="item-content-first">
<JssImage field={item.fields?.Thumbnail?.jsonValue} />
</div>
<div className="item-content-second">
<div className="item-title">
<DateField
field={item.fields?.ReleaseDate?.jsonValue}
render={(date) => date?.toLocaleDateString()}
/>{" "}
<Text field={item.fields?.Title?.jsonValue} />
</div>
<div className="item-overview">
<RichText field={item.fields?.Overview?.jsonValue} />
</div>
</div>
</>
</RecentItem>
))}
</ul>
<div className="field-recentlink">
<JssLink field={props.fields.ListLink} />
</div>
</div>
</div>
);
};
export const getStaticProps: GetStaticComponentProps = async (
rendering,
layoutData
) => {
try {
const context = layoutData?.sitecore?.context;
const route = layoutData?.sitecore?.route;
const params = rendering?.params;
const fields = rendering?.fields;
if (!context || !route || !params || !fields) {
return { articles: [] as RecentItemProps[] };
}
const graphQLClient = graphqlClientFactory();
const language = context.language ?? config.defaultLanguage;
const pageDisplayCount = Number(params.PageDisplayCount);
if (
pageDisplayCount &&
Number.isInteger(pageDisplayCount) &&
pageDisplayCount < 1
) {
return { articles: [] as RecentItemProps[] };
}
const rootItem = fields.Root as Item;
if (!rootItem) {
return { articles: [] as RecentItemProps[] };
}
const rootId = rootItem.id;
const data = await graphQLClient.request<SearchQueryData>(RecentQuery, {
rootId,
language,
pageDisplayCount,
});
const articles: RecentItemProps[] =
data.search?.results?.map((d) => {
return {
id: d.id,
fields: {
Title: d.Title,
Thumbnail: d.Thumbnail,
Overview: d.Overview,
ReleaseDate: d.ReleaseDate,
},
url: d.url?.path,
};
}) ?? [];
return { articles };
} catch (e) {
console.error(e);
return { articles: [] as RecentItemProps[] };
}
};
使用イメージ
Page builder でコンポーネントを配置すると、次のように表示されます。

見出しとリンクの入力欄は任意に編集してください。
また、Root フィールドは Page builder またはコンテンツ エディタから設定することができます。

トラブルシューティング
もしもコンポーネントを配置したページが 500 エラーになってしまう場合、tsx ファイルのデータの受け取り方、またはその型定義が間違っている可能性が非常に高いです。
その時は、以下の手順で確認することができます。
- tsx ファイルの Default 関数の中身を全てコメントアウトし、以下のコードを追加します
console.log(props); return <p>My Component</p>;
- 500 エラーが出ているページを更新し開発者ツールを開きます
- コンポーネントが受け取る props オブジェクトがコンソールに表示されるので、データの構造や型が合っているか確認します
以上で、手順は完了です。
ここまでお読みいただきありがとうございました。
参考ドキュメント
- Use GraphQL to fetch component-level data in JSS Next.js apps | Sitecore Documentation
- Component-level data fetching in JSS Next.js apps | Sitecore Documentation
このコンポーネント以外にも、イーストではXM Cloud用に作成した独自のコンポーネントをGitHubで公開中です!
以下のeast-xmcloud-extensionsのバナーからご覧ください。