アプリ内でSitecoreAIのコンテンツをレンダリングする

日本語翻訳に関する免責事項

このページの翻訳はAIによって自動的に行われました。可能な限り正確な翻訳を心掛けていますが、原文と異なる表現や解釈が含まれる場合があります。正確で公式な情報については、必ず英語の原文をご参照ください。

!注このドキュメントの改善にご協力くださいフレームワークに依存しないドキュメントは現在開発中です。コンテンツ改善の提案があれば、このページの下部で フィードバック を共有してください。

このウォークスルーでは、フロントエンドアプリケーションでSitecoreAIのコンテンツをレンダリングする方法を説明しており、これはSitecoreの開発における重要な作業です。ウォークスルーの終わりには、フロントエンドアプリがlocalhostでSitecoreAIページビルダーのコンテンツをレンダリングします。

このウォークスルーでは、Astro 7およびGoのフロントエンドアプリケーションのコード例を提供し、以下の方法を説明します:

  1. Sitecoreの環境変数を設定しましょう
  2. GraphQLを使ってJSON形式のレイアウトデータを取得
  3. ページルーティングの設定
  4. ビルドコンポーネント
  5. ハンドル404ページ
  6. アプリをテストしてください:::

!注始める前に

Sitecoreの環境変数を設定しましょう

単一のページのレイアウトを要求するには一連の識別子が必要です。これらの識別子は変数としてGraphQLのレイアウトリクエストに渡すのがベストプラクティスです。Nodeベースのアプリケーションでは、これらは通常 .envファイルに保存されますが、構築する言語やフレームワークに適した方法を使うべきです。

!重要APIキー、編集シークレット、コンテキストIDなどの秘密は、決してハードコーディングされたりブラウザに露出させたりしてはなりません。代わりに環境変数やシークレットマネージャーに格納するのがベストプラクティスです。GraphQL APIとやり取りするために、サーバー側の操作に対する敏感なAPIアクセスを制限するために、Edge向けのスコープ付きコンテキストIDを作成することをお勧めします。

名称

概要

SITECORE_EDGE_PLATFORM_URL

プレビュー GraphQL APIとデリバリー GraphQL APIエンドポイント。

値を以下に設定します:

https://edge-platform.sitecorecloud.io/v1/content/api/graphql/v1

SITECORE_EDGE_CONTEXT_ID

あなたの環境のプレビューコンテキストIDかライブコンテキストID。

SitecoreAI Deploy > Projects > Deployment > Authoring Environments > あなたの環境 > 詳細

プレビューコンテキストIDを使用して、未公開(ドラフト)および公開されたコンテンツの両方にアクセスでき、地域開発に推奨されます。ライブコンテキストIDを使って公開コンテンツのみにアクセスしてください。

0123456789abcdefghijkl

SITECORE_SITE_NAME

あなたのサイトのシステム名です。

SitecoreAI Deploy > Projects > > Deployment Authoring Environments > Environment > Sites Deployment Projects の価値を見出してください。

my-site

SITECORE_SITE_LANGUAGE_CODE

あなたのサイトの言語コードです。言語コード>設定SitecoreAI >で値を>してください

あなたのサイトが利用可能な言語のコードを使ってください。利用可能な言語は、SitecoreAIページビルダーでサイトを開き、上部ツールバーの言語ドロップダウンをクリックすると確認できます。

  • en
  • de-DE
  • ja-JP

GraphQLを使ってJSON形式のレイアウトデータを取得

環境変数を設定したら、GraphQLクエリを始められます。このステップでは、サイトのルートページのレイアウトデータ(ページ表現)を取得し、SitecoreAIがアプリに送る生のJSONを確認できるようにします。

以下のGraphQLクエリは、ページのレイアウトデータを取得することを可能にします:

query LayoutQuery($site: String!, $language: String!, $routePath: String!) { layout(site: $site, language: $language, routePath: $routePath) { item { rendered # Returns layout data } } }

このクエリでは、ページが属するサイト、ページの言語バージョン、ページへの経路を指定する必要があります。

フレームワークを選択し、フロントエンドアプリでこのクエリを作成する手順に従ってください:

::::タブズ:::tab{title="Astro"}

  1. クエリを作成するには、src/services/sitecoreClient.jsを作成し、以下のコードを貼り付けます。

    // src/services/sitecoreClient.js

    export async function fetchLayoutData(site, language, routePath) { const env = import.meta.env;

    const endpoint = env.SITECORE_EDGE_PLATFORM_URL; const contextId = env.SITECORE_EDGE_CONTEXT_ID;

    // Create the GraphQL query: const query = ` query LayoutQuery($site: String!, $language: String!, $routePath: String!) { layout(site: $site, language: $language, routePath: $routePath) { item { rendered } } } `;

    const variables = { site, language, routePath, };

    /* Optionally, add if-statements to check for missing environment variables. */

    // Include the Context ID in the header: const headers = { "Content-Type": "application/json", "x-sitecore-contextid": contextId, };

    // Make the GraphQL request: const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify({ query, variables }), });

    const result = await response.json();

    if (result.errors) { throw new Error(JSON.stringify(result.errors)); }

    const rendered = result?.data?.layout?.item?.rendered; return typeof rendered === "string" ? JSON.parse(rendered) : rendered; }

  2. フロントエンドアプリのルートページが読み込まれたときにクエリを行うには、src/pages/index.astroの内容を以下のコードに置き換えてください:

    --- // src/pages/index.astro import { fetchLayoutData } from "../services/sitecoreClient.js";

    const site = import.meta.env.SITECORE_SITE_NAME; const language = import.meta.env.SITECORE_SITE_LANGUAGE_CODE || "en"; const routePath = "/"; const layoutData = await fetchLayoutData(site, language, routePath);

    {JSON.stringify(layoutData, null, 2)}

    localhostでプロジェクトのルートページを開くと、ページはGraphQLクエリを行い、SitecoreAIが返す生のJSONを表示します。

  3. 開発サーバーを起動し、localhostでプロジェクトのホームページを開きます。生のJSON表示です。これはSitecoreAIページビルダーの ホームページ の実際のページレイアウトデータです。::::::tab{title="Go"}

  4. クエリを作成するには、sitecore.goを作成し、以下のコードを貼り付けます。

    // sitecore.go package main

    import ( "bytes" "encoding/json" "fmt" "net/http" "os" )

    const layoutQuery = ` query LayoutQuery($site: String!, $language: String!, $routePath: String!) { layout(site: $site, language: $language, routePath: $routePath) { item { rendered } } }`

    // layoutResponse is the top-level shape of a GraphQL layout query response. type layoutResponse struct { Data struct { Layout struct { Item *struct { Rendered json.RawMessage `json:"rendered"` } `json:"item"` } `json:"layout"` } `json:"data"` Errors struct { Message string `json:"message"` } `json:"errors"` }

    // fetchLayoutData retrieves the Sitecore layout snapshot // for the given site name, language, and route path. // Returns (nil, nil) when the route does not exist in Sitecore. func fetchLayoutData(site, language, routePath string) (mapstringany, error) { endpoint := os.Getenv("SITECORE_EDGE_PLATFORM_URL") contextID := os.Getenv("SITECORE_EDGE_CONTEXT_ID")

    payload, err := json.Marshal(mapstringany{ "query": layoutQuery, "variables": mapstringany{ "site": site, "language": language, "routePath": routePath, }, }) if err != nil { return nil, fmt.Errorf("marshal: %w", err) }

    req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("new request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("x-sitecore-contextid", contextID)

    resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("edge request: %w", err) } defer resp.Body.Close()

    var gql layoutResponse if err := json.NewDecoder(resp.Body).Decode(&gql); err != nil { return nil, fmt.Errorf("decode: %w", err) } if len(gql.Errors) > 0 { return nil, fmt.Errorf("graphql: %s", gql.Errors0.Message) } if gql.Data.Layout.Item == nil { return nil, nil // route does not exist in Sitecore }

    // `rendered` can arrive as a JSON object or a JSON-encoded string. raw := gql.Data.Layout.Item.Rendered var out mapstringany if json.Unmarshal(raw, &out) == nil { return out, nil } // Fall back: rendered was a quoted string - decode and re-parse it. var s string if json.Unmarshal(raw, &s) != nil { return nil, fmt.Errorf("cannot parse rendered field") } if err := json.Unmarshal(byte(s), &out); err != nil { return nil, fmt.Errorf("cannot parse rendered string: %w", err) } return out, nil }

  5. フロントエンドアプリのルートページが読み込まれたときにクエリを作成するには、main.goを作成し、以下のコードを貼り付けてください:

    // main.go package main

    import ( "encoding/json" "fmt" "log" "net/http" "os" "github.com/joho/godotenv" )

    func main() { // Load .env file if present (ignored when not found, e.g. in production). _ = godotenv.Load()

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { site := os.Getenv("SITECORE_SITE_NAME") language := os.Getenv("SITECORE_SITE_LANGUAGE_CODE") if language == "" { language = "en" }

    data, err := fetchLayoutData(site, language, "/") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }

    pretty, err := json.MarshalIndent(data, "", " ") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprint(w, string(pretty)) })

    log.Println("Listening on http://localhost:3000") log.Fatal(http.ListenAndServe("

    ", nil)) }

    localhostでプロジェクトのルートページを開くと、ページはGraphQLクエリを行い、SitecoreAIが返す生のJSONを表示します。

  6. 開発サーバーを起動:

    go run .

  7. localhostでプロジェクトのホームページを開くhttp://localhost:3000/。生のJSON表示です。これはSitecoreAIページビルダー内の ホームページ の実際のページレイアウトデータです。

生のJSONは以下の通りです。レスポンス構造やコンポーネントの表現方法の詳細については、「 レイアウトクエリとレスポンス」を参照してください:::::::

{ "sitecore": { "context": { /* metadata */ }, "route": { "name": "Home", "displayName": "Home", "fields": { /* Page-level content fields (title, summary, thumbnail, keywords, navigation information, etc.) */ }, "placeholders": { "headless-header": /* Array of components for page header */ , "headless-main": /* Array of components for page main section */ , "headless-footer": /* Array of components for page footer */ } } } }

コンポーネントの構造:

{ "uid": "<UNIQUE_COMPONENT_ID>", "componentName": "<COMPONENT_NAME>", "dataSource": "<DATA_SOURCE_PATH>", "params": { /* Display parameters (styles, modes) */ }, "fields": { /* Content data for this component */ }, "placeholders": { /* Nested children components (recursive structure) */ "nested-placeholder-name": /* More components */ } }

ページルーティングの設定

アプリが単一のパスのレイアウトデータを取得できるようになったので、任意のURLパスのレイアウトデータを取得し、Sitecoreに存在しないパスを /404にリダイレクトするキャッチオールルートを設定します。

開発サーバーを再起動すると、localhostのフロントエンドアプリは ホーム ページの生のJSON(root)をレンダリングしますが、他のページのJSONはまだレンダリングできません。サイトのすべてのURLパスをレンダリングするためにページルーティングを設定する必要があります。

!注ホームページ(ルート)は単一のフォワードスラッシュ /に対応しているため、ルートには必ず先頭のフォワードスラッシュを含める必要があります。例えば、Home > Productsページへのルーティングには、文字列の冒頭にスラッシュを入れたまなぞ /productsルートを使います。

このステップのルーティングコードは、Sitecoreに存在しないルートに対して /404への簡単なリダイレクトを使用します。これは意図的なプレースホルダーです。後日の手順で、適切なSitecore管理の404ページに置き換えます。

::::タブズ:::tab{title="Astro"}

Astroの動的ルートはサーバーレンダリングやgetStaticPaths()の使用が必要です。このウォークスルーはexport const prerender = false;によるサーバーレンダリングを使用しています。

  1. キャッチオールルートsrc/pages/...slug.astroを作成し、ルーティングロジックを実装します:

    --- // src/pages/...slug.astro import { fetchLayoutData } from '../services/sitecoreClient.js';

    export const prerender = false;

    const site = import.meta.env.SITECORE_SITE_NAME; const language = import.meta.env.SITECORE_SITE_LANGUAGE_CODE || "en"; const { slug } = Astro.params; const routePath = slug ? `/${slug}` : '/';

    let layoutData; try { layoutData = await fetchLayoutData(site, language, routePath); } catch (error) { return Astro.redirect('/404'); }

    if (!layoutData) { return Astro.redirect('/404'); }

    {JSON.stringify(layoutData, null, 2)}
  2. 開発サーバーを再起動し、ルートとは異なるルート(例えば /about)がそのページの生のJSONが表示されるか確認してください。

  1. ルーティングロジックを実装するために、main.goの内容を以下のコードに置き換えます。

    // main.go package main

    import ( "encoding/json" "fmt" "log" "net/http" "os" "strings" "github.com/joho/godotenv" )

    func main() { // Load .env file if present (ignored when not found, e.g. in production). _ = godotenv.Load()

    site := os.Getenv("SITECORE_SITE_NAME") language := os.Getenv("SITECORE_SITE_LANGUAGE_CODE") if language == "" { language = "en" }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Derive the Sitecore route path from the URL. Keep root "/" as-is; // trim any trailing slash from other paths. routePath := r.URL.Path if routePath != "/" { routePath = strings.TrimRight(routePath, "/") }

    data, err := fetchLayoutData(site, language, routePath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if data == nil { // Route does not exist in Sitecore. if routePath == "/404" { // Sitecore 404 page is not configured; serve a minimal fallback. http.NotFound(w, r) return } http.Redirect(w, r, "/404", http.StatusFound) return }

    pretty, err := json.MarshalIndent(data, "", " ") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprint(w, string(pretty)) })

    log.Println("Listening on http://localhost:3000") log.Fatal(http.ListenAndServe("

    ", nil)) }

    レイアウトコンポーネントが設定された後、後でこのファイルを更新してrenderLayoutを呼び出します。

  2. 開発サーバーを再起動し、ルートとは異なるルート(例えば /about)がそのページの生のJSONが表示されるか確認してください。

ビルドコンポーネント

コンポーネントマッピングと欠けているコンポーネントのフォールバックを構築します

個々のコンポーネントを実装する前に、まず2つの基盤が必要です。1つは、Sitecore componentName文字列をコンポーネント実装に結びつけるコンポーネントマッピングと、まだマッピングに含まれていないcomponentNameを処理するフォールバックコンポーネントです。

コンポーネントマッピングは、レイアウトデータに "RichText"という名前のコンポーネントが含まれている場合、アプリが正しい実装をレンダリングすることを保証します。フォールバックコンポーネントは、コンポーネント名を表示するエラーボックスを表示するため、開発中にマッピングされていないコンポーネントを即座に表示できるようにします。

新しいコンポーネントを追加するのは常に同じ2ステップのパターンです

、それをマップに追加する。このウォークスルーのすべてのコンポーネントはこのパターンに従っています。

!注欠落コンテンツの扱いcomponentNameがコンポーネントマップにマッピングされていない場合、Placeholderコンポーネントが実際のコンポーネントではなくフォールバックをレンダリングします。フォールバックはコンポーネント名を示すスタイリングされたエラーボックスを表示します。入れ子のプレースホルダー子はレンダリングされません。

これにより、開発中に欠落したコンポーネントが即座に見え、どのコンポーネントを実装する必要があるかを正確に把握できます。

欠けているコンポーネントのコンテンツを表示させるには、そのコンポーネントが独自のfieldsをレンダリングするように実装し、それをコンポーネントマップにレジスタジアムします。マッピング後、実際の実装がエラーボックスを置き換えます。

コンポーネントマッピングと欠損コンポーネントのフォールバックを作成するには:

::::タブズ:::tab{title="Astro"}

  1. src/components/componentMap.jsを作成し、以下のコードを貼り付けます:

    // src/components/componentMap.js // Import your component implementations here, then add them to the map.

    export const componentMap = { // Register component implementations here as you build them. // Example: 'ComponentName': ComponentImplementation };

    コンポーネントを実装する際、後の手続きでこのマップにエントリを追加していきます。

  2. src/components/MissingComponent.astroを作成し、以下のコードを貼り付けます:

    --- // src/components/MissingComponent.astro

    const { componentName } = Astro.props;

    {componentName}

    Component is not implemented. Add an implementation and register it in the component map.

  1. components.goを作成し、以下のコードを貼り付けます:

    // components.go package main

    import "html/template"

    // ComponentFunc is the signature every component renderer must satisfy. type ComponentFunc func(fields, params mapstringany, placeholders mapstringany) template.HTML

    // componentRegistry maps Sitecore componentName values to their Go render // functions. Add an entry here whenever you implement a new component. var componentRegistry mapstringComponentFunc

    func init() { componentRegistry = mapstringComponentFunc{ // Register component implementations here as you build them. } }

    // field helpers

    // strField reads the "value" string of a plain-text or rich-text field. func strField(fields mapstringany, name string) string { f, _ := fieldsname.(mapstringany) v, _ := f"value".(string) return v }

    // imgValue reads an Image field's value object, with jsonValue as a fallback // for direct item queries. func imgValue(fields mapstringany, name string) mapstringany { f, _ := fieldsname.(mapstringany) if v, ok := f"value".(mapstringany); ok { return v } if jv, ok := f"jsonValue".(mapstringany); ok { v, _ := jv"value".(mapstringany) return v } return nil }

    // lnkValue reads a General Link field's value object, with jsonValue fallback. func lnkValue(fields mapstringany, name string) mapstringany { f, _ := fieldsname.(mapstringany) if v, ok := f"value".(mapstringany); ok { return v } if jv, ok := f"jsonValue".(mapstringany); ok { v, _ := jv"value".(mapstringany) return v } return nil }

    // ms reads a string value from any mapstringany by key. func ms(m mapstringany, key string) string { v, _ := mkey.(string) return v }

    func esc(s string) string { return template.HTMLEscapeString(s) }

    strField、imgValue、lnkValueフィールドヘルパーは、レイアウトデータのfieldsオブジェクトから入力された値を読み取っていました。次の手順でコンポーネントレンダラーを実装する際に使います。

    !注この解説の例は、分かりやすくするためにヘルパーコードとコンポーネントレンダラーを少数のファイルにまとめています。本番のコードベースでは、通常、各コンポーネントに独自のファイルを与えます。

  2. missingComponent.goを作成し、以下のコードを貼り付けます:

    // missingComponent.go package main

    import ( "fmt" "html/template" )

    func renderMissingComponent(componentName string, _ mapstringany) template.HTML { return template.HTML(fmt.Sprintf( `

    %s

    Component is not implemented. Add an implementation and register it in the component map.

    `, template.HTMLEscapeString(componentName), )) }

プレースホルダーとページレイアウトを作成します

コンポーネントマッピングとフォールバックが揃ったことで、Placeholderコンポーネントとページレイアウトを構築できます。 Placeholderコンポーネントはレンダリングパイプラインのエンジンです。レイアウトデータから名前付きのプレースホルダースロットとそのコンポーネント配列を取り、マッピング内の各コンポーネントを調べてレンダリングします。コンポーネントは入れ子状のプレースホルダーを含む可能性があるため、Placeholderはページ全体で再帰的に呼ばれます。

ページレイアウトコンポーネントは、3つのトップレベルのプレースホルダー(headless-header、headless-main、headless-footer)をPlaceholderコンポーネントに結びつけ、最終的なHTMLページを生成します。ページ上の他のすべてのプレースホルダーは再帰的Placeholderコンポーネントによってレンダリングされます。

このステップの後、アプリはすべてのページをレンダリングパイプライン全体にルーティングします。コンポーネントはまだ意味のあるコンテンツを表示できません。なぜなら、マッピングにコンポーネント実装が登録されていないからです。次の手順でコンポーネントを実装します。

プレースホルダーとページレイアウトを作成するには:

::::タブズ:::tab{title="Astro"}

  1. src/components/Placeholder.astroを作成し、以下のコードを貼り付けます:

    --- // src/components/Placeholder.astro import { componentMap } from './componentMap.js'; import MissingComponent from './MissingComponent.astro';

    const { name, rendering } = Astro.props; // Type casts are needed because the component map is keyed dynamically // and rendering items are untyped layout data. const map = componentMap as Record<string, any>; const items: any = rendering ?? ;

    {items.map((component) => { const Component = mapcomponent.componentName ?? MissingComponent;

    return ( ); })}

    Placeholderコンポーネントはプレースホルダー名とそのコンポーネント配列を受け取り、各componentNameに適した実装を見つけ、fields、params、placeholdersを通過します。これにより、子コンポーネントは自分のネストされたプレースホルダーをレンダリングできます。

  2. src/components/Layout.astroを作成し、以下のコードを貼り付けます:

    --- // src/components/Layout.astro import Placeholder from './Placeholder.astro';

    interface Props { layoutData: any; }

    const { layoutData } = Astro.props; const route = layoutData?.sitecore?.route; const placeholders = route?.placeholders || {};

    {route ? ( <>
    ) : (

    Layout data is missing for this route.

    )}
  3. src/pages/index.astroを更新して、生のJSONではなくLayoutコンポーネントをレンダリングする:

    --- // src/pages/index.astro import SitecoreLayout from '../components/Layout.astro'; import { fetchLayoutData } from '../services/sitecoreClient.js';

    const site = import.meta.env.SITECORE_SITE_NAME; const language = import.meta.env.SITECORE_SITE_LANGUAGE_CODE || "en"; const routePath = "/"; const layoutData = await fetchLayoutData(site, language, routePath);

  4. src/pages/...slug.astroを更新して、生のJSONではなくLayoutコンポーネントをレンダリングする:

    --- // src/pages/...slug.astro import { fetchLayoutData } from '../services/sitecoreClient.js'; import SitecoreLayout from '../components/Layout.astro';

    export const prerender = false;

    const site = import.meta.env.SITECORE_SITE_NAME; const language = import.meta.env.SITECORE_SITE_LANGUAGE_CODE || "en"; const { slug } = Astro.params; const routePath = slug ? `/${slug}` : '/';

    let layoutData; try { layoutData = await fetchLayoutData(site, language, routePath); } catch (error) { return Astro.redirect('/404'); }

    if (!layoutData) { return Astro.redirect('/404'); }

  1. placeholder.goを作成し、以下のコードを貼り付けます:

    // placeholder.go package main

    import ( "fmt" "html/template" "strings" )

    // renderPlaceholder renders every component in a named placeholder slot. func renderPlaceholder(name string, items any) template.HTML { var sb strings.Builder for _, item := range items { comp, ok := item.(mapstringany) if !ok { continue } sb.WriteString(string(renderComponent(comp))) } return template.HTML(fmt.Sprintf( `

    %s
    `, template.HTMLEscapeString(name), sb.String(), )) }

    // renderComponent dispatches one layout component object to its registered // renderer, or falls back to renderMissingComponent for unknown names. func renderComponent(comp mapstringany) template.HTML { componentName, _ := comp"componentName".(string) fields, _ := comp"fields".(mapstringany) params, _ := comp"params".(mapstringany) rawPH, _ := comp"placeholders".(mapstringany)

    if fields == nil { fields = mapstringany{} } if params == nil { params = mapstringany{} } // Coerce the nested placeholder arrays from any to a typed slice. placeholders := make(mapstringany, len(rawPH)) for k, v := range rawPH { if arr, ok := v.(any); ok { placeholdersk = arr } }

    if fn, ok := componentRegistrycomponentName; ok { return fn(fields, params, placeholders) } return renderMissingComponent(componentName, placeholders) }

    Placeholderコンポーネントはプレースホルダー名とそのコンポーネント配列を受け取り、各componentNameに適した実装を見つけ、fields、params、placeholdersを通過します。これにより、子コンポーネントは自分のネストされたプレースホルダーをレンダリングできます。

  2. Layout.goを作成し、以下のコードを貼り付けます:

    // layout.go package main

    import ( "html/template" "io" )

    var pageTmpl = template.Must(template.New("page").Parse(`

    {{- if .HasRoute}}
    {{.Header}}
    {{.Main}}
    {{.Footer}}
    {{- else}}

    Layout data is missing for this route.

    {{- end}} \`))

    type pageVars struct { Title string HasRoute bool Header template.HTML Main template.HTML Footer template.HTML }

    // renderLayout writes a complete HTML page to w using the provided layout data. // Fields of type template.HTML are rendered as-is by html/template (no escaping), // which is intentional. Our component renderers produce safe, trusted HTML. func renderLayout(w io.Writer, layoutData mapstringany) error { if layoutData == nil { return pageTmpl.Execute(w, pageVars{Title: "Page not found"}) } sitecore, _ := layoutData"sitecore".(mapstringany) route, _ := sitecore"route".(mapstringany) if route == nil { return pageTmpl.Execute(w, pageVars{Title: "Page not found"}) }

    phs, _ := route"placeholders".(mapstringany) slot := func(key string) any { arr, _ := phskey.(any) return arr }

    title, _ := route"displayName".(string) if title == "" { title, _ = route"name".(string) } return pageTmpl.Execute(w, pageVars{ Title: title, HasRoute: true, Header: renderPlaceholder("headless-header", slot("headless-header")), Main: renderPlaceholder("headless-main", slot("headless-main")), Footer: renderPlaceholder("headless-footer", slot("headless-footer")), }) }

  3. 生JSONレンダリングの代わりにrenderLayoutを呼び出すように更新main.go:

    // main.go package main

    import ( "log" "net/http" "os" "strings" "github.com/joho/godotenv" )

    func main() { // Load .env file if present (ignored when not found, e.g. in production). _ = godotenv.Load()

    site := os.Getenv("SITECORE_SITE_NAME") language := os.Getenv("SITECORE_SITE_LANGUAGE_CODE") if language == "" { language = "en" }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Derive the Sitecore route path from the URL. Keep root "/" as-is; // trim any trailing slash from other paths. routePath := r.URL.Path if routePath != "/" { routePath = strings.TrimRight(routePath, "/") }

    data, err := fetchLayoutData(site, language, routePath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if data == nil { // Route does not exist in Sitecore. if routePath == "/404" { // Sitecore 404 page is not configured; serve a minimal fallback. http.NotFound(w, r) return } http.Redirect(w, r, "/404", http.StatusFound) return }

    w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := renderLayout(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } })

    log.Println("Listening on http://localhost:3000") log.Fatal(http.ListenAndServe("

    ", nil)) }

組み込みコンポーネントを実装する

コンポーネントはレイアウトデータ内のfieldsオブジェクトから内容を読み取っています。各フィールドには型があり、それぞれの型は異なるJSON形状を生成します。このステップでは、組み込みのSitecoreコンポーネントのうち3つを実装します: Title、RichText、Image。各コンポーネントは、1つ以上の フィールドタイプの読み取りとレンダリングを示します。

  • Title - プレーンテキストフィールドを表示します(heading)。例: { "value": "Hello World" }
  • RichText - リッチテキストフィールド(Text)をレンダリングします。値はHTMLマークアップで、エスケープされたテキストではなく生のHTMLとして注入する必要があります。例: { "value": "

    HTML content

    " }
  • Image - 組み込みの Image コンポーネントを構成する3つのフィールドをレンダリングします。画像フィールド(Image)、プレーンテキストキャプションフィールド(ImageCaption)、そして画像をリンクでラップするGeneral Linkフィールド(TargetUrl)です。

組み込みコンポーネントを実装するには:

::::タブズ:::tab{title="Astro"}

  1. Titleコンポーネントを作成する:

    --- // src/components/Title.astro interface Props { fields: { heading?: { value?: string }; }; params?: Record<string, any>; }

    const { fields, params } = Astro.props;

    {fields?.heading?.value && (

    {fields.heading.value}

    日本語翻訳に関する免責事項

    このページの翻訳はAIによって自動的に行われました。可能な限り正確な翻訳を心掛けていますが、原文と異なる表現や解釈が含まれる場合があります。正確で公式な情報については、必ず英語の原文をご参照ください。

    )}
  2. RichTextコンポーネントを作成する:

    --- // src/components/RichText.astro

    interface Props { fields: { Text?: { value?: string }; }; }

    const { fields } = Astro.props; const html = fields?.Text?.value ?? "";

  3. Imageコンポーネントを作成する:

    --- // src/components/Image.astro

    interface Props { fields: { Image?: { value?: { src?: string; alt?: string; width?: string; height?: string }; jsonValue?: { value?: { src?: string; alt?: string; width?: string; height?: string }; }; }; ImageCaption?: { value?: string }; TargetUrl?: { value?: { href?: string; target?: string }; jsonValue?: { value?: { href?: string; target?: string } }; }; }; }

    const { fields } = Astro.props;

    // Image field, with jsonValue as a fallback for direct item queries. const imageField = fields?.Image; const imageValue = imageField?.value ?? imageField?.jsonValue?.value; const { src, alt, width, height } = imageValue ?? {}; const caption = fields?.ImageCaption?.value;

    {src && (

    {link?.href ? ( {alt ) : ( {alt )} {caption &&
    {caption}
    }
    )}
  4. コンポーネントマッピングに3つのコンポーネントすべてを更新して登録しますsrc/components/componentMap.js:

    // src/components/componentMap.js import Image from './Image.astro'; import RichText from './RichText.astro'; import Title from './Title.astro'; // Import all your other components here

    export const componentMap = { 'Image': Image, 'RichText': RichText, 'Title': Title, // Map all your other components here };

  1. components.goでは、インポートブロックを更新して"fmt"を追加します:

    // Updated import block for components.go: import ( "fmt" "html/template" )

  2. Titleコンポーネント用のレンダー関数を追加してください:

    func renderTitle(fields, params mapstringany, _ mapstringany) template.HTML { heading := strField(fields, "heading") if heading == "" { return "" } classAttr := "" if cssClass := ms(params, "cssClass"); cssClass != "" { classAttr = fmt.Sprintf(` class="%s"`, esc(cssClass)) } return template.HTML(fmt.Sprintf("<h1%s>%s", classAttr, esc(heading))) }

  3. RichTextコンポーネント用のレンダー関数を追加してください:

    func renderRichText(fields, params mapstringany, _ mapstringany) template.HTML { html := strField(fields, "Text") if html == "" { return "" } return template.HTML("

    " + html + "
    ") }

  4. Imageコンポーネント用のレンダー関数を追加してください:

    func renderImage(fields, params mapstringany, _ mapstringany) template.HTML { img := imgValue(fields, "Image") if img == nil || ms(img, "src") == "" { return "" } imgTag := fmt.Sprintf( `%s`, esc(ms(img, "src")), esc(ms(img, "alt")), esc(ms(img, "width")), esc(ms(img, "height")), ) // Wrap the image in a link if TargetUrl is set. lnk := lnkValue(fields, "TargetUrl") var content string if lnk != nil && ms(lnk, "href") != "" { targetAttr := "" if target := ms(lnk, "target"); target != "" { targetAttr = fmt.Sprintf(` target="%s"`, esc(target)) } content = fmt.Sprintf(`<a href="%s"%s>%s`, esc(ms(lnk, "href")), targetAttr, imgTag) } else { content = imgTag } // Show a caption below the image if ImageCaption is set. caption := strField(fields, "ImageCaption") if caption != "" { return template.HTML(fmt.Sprintf("

    %s
    %s
    ", content, esc(caption))) } return template.HTML(fmt.Sprintf("
    %s
    ", content)) }

  5. components.goでは、init()関数を更新して3つのコンポーネントすべてを登録します:

    func init() { componentRegistry = mapstringComponentFunc{ "Image": renderImage, "RichText": renderRichText, "Title": renderTitle, // Add entries here as you implement each component } }

Promoコンポーネントを実装してください

Promoは、複数のフィールド(PromoText、PromoText2、またはPromoText3)からリッチテキストをレンダリングするコンテンツコンポーネントです。これは、表示するコンテンツを探す際に、コンポーネントが複数のフィールド名にまたがってフォールバックできる仕組みを示しています。

Promoコンポーネントを実装するために:

::::タブズ:::tab{title="Astro"}

  1. Promoコンポーネントを作成する:

    --- // src/components/Promo.astro

    interface Props { fields: { PromoText?: { value?: string }; PromoText2?: { value?: string }; PromoText3?: { value?: string }; }; }

    const { fields } = Astro.props; const html = fields?.PromoText?.value || fields?.PromoText2?.value || fields?.PromoText3?.value || "";

    {html &&

    }

  2. コンポーネントマッピングにPromoを次のインポートとエントリーで更新してsrc/components/componentMap.js登録します:

    import Promo from './Promo.astro';

    export const componentMap = { // ...existing entries... 'Promo': Promo, };

  1. components.goでは、Promoコンポーネントのレンダリング関数を追加します:

    func renderPromo(fields, params mapstringany, _ mapstringany) template.HTML { for _, name := range string{"PromoText", "PromoText2", "PromoText3"} { if html := strField(fields, name); html != "" { return template.HTML(html) // rich-text: inject as raw HTML, not escaped } } return "" }

  2. Promo登録するには、以下のエントリーを追加してcomponentRegistry:

    "Promo": renderPromo,

ColumnSplitterコンポーネントを実装してください

ColumnSplitterは、自身のフィールドコンテンツをレンダリングするのではなく、ネストされたプレースホルダーを管理する構造的コンポーネントです。コンテンツ領域を列に分割し、各列を名前付きのプレースホルダーとして公開することで、コンテンツ作成者が各列内に他のコンポーネントを配置できるようにします。 ColumnSplitterの実装は、このウォークスルーで先に確立した再帰的レンダリングの概念を強化します。 Placeholderコンポーネントは各列の内容を処理するため、コンポーネントマップに登録された任意のコンポーネントを列内にネストすることができます。

ColumnSplitterコンポーネントを実装するために:

::::タブズ:::tab{title="Astro"}

  1. ColumnSplitterコンポーネントを作成する:

    --- // src/components/ColumnSplitter.astro import Placeholder from './Placeholder.astro';

    interface Props { params?: Record<string, any>; placeholders?: Record<string, any>; }

    const { params, placeholders } = Astro.props; const enabled = typeof params?.EnabledPlaceholders === 'string' ? params.EnabledPlaceholders.split(',').map((value) => value.trim()).filter(Boolean) : null; type Column = { key: string; items: any; index: number }; const columns = (Object.entries(placeholders ?? {}) .map((key, items) => { const match = key.match(/^column-(\d+)-/); if (!match) return null;

    return { key, items, index: Number(match1), }; }) .filter((c): c is Column => c !== null) .filter((column) => !enabled || enabled.includes(String(column.index))) .sort((a, b) => a.index - b.index)); const wrapperClass = params?.GridParameters.filter(Boolean).join(' ');

    {columns.map((column) => { const columnClass = params?.\`ColumnWidth${column.index}\`;

    return (

    ); })}
  2. コンポーネントマッピングにColumnSplitterを次のインポートとエントリーで更新してsrc/components/componentMap.js登録します:

    import ColumnSplitter from './ColumnSplitter.astro';

    export const componentMap = { // ...existing entries... 'ColumnSplitter': ColumnSplitter, };

  1. columnsplitter.goを作成し、以下のコードを貼り付けます:

    // columnsplitter.go package main

    import ( "fmt" "html/template" "sort" "strconv" "strings" )

    func renderColumnSplitter(fields, params mapstringany, placeholders mapstringany) template.HTML { // Determine which column indices are enabled (params value: "1,2,3"). var enabled string if ep := ms(params, "EnabledPlaceholders"); ep != "" { for _, p := range strings.Split(ep, ",") { enabled = append(enabled, strings.TrimSpace(p)) } }

    type col struct { index int key string items any } var cols col for key, items := range placeholders { // Column placeholder keys look like "column-N-{uid}". if !strings.HasPrefix(key, "column-") { continue } rest := keylen("column-"): dash := strings.Index(rest, "-") if dash < 0 { continue } idx, err := strconv.Atoi(rest

    ) if err != nil { continue } idxStr := strconv.Itoa(idx) if len(enabled) > 0 { found := false for _, e := range enabled { if e == idxStr { found = true break } } if !found { continue } } cols = append(cols, col{index: idx, key: key, items: items}) } sort.Slice(cols, func(i, j int) bool { return colsi.index < colsj.index })

    var sb strings.Builder sb.WriteString(fmt.Sprintf(`

    `, esc(ms(params, "GridParameters")))) for _, c := range cols { colClass := ms(params, fmt.Sprintf("ColumnWidth%d", c.index)) sb.WriteString(fmt.Sprintf(`
    `, c.index, esc(colClass))) sb.WriteString(string(renderPlaceholder(c.key, c.items))) sb.WriteString("
    ") } sb.WriteString("
    ") return template.HTML(sb.String()) }

  2. components.goでは、init()関数のcomponentRegistryにColumnSplitterを加えます:

    !注renderColumnSplitterrenderPlaceholderと呼ばれ、componentRegistryと読み取られます。だからこそcomponentRegistryは文字通りの地図ではなく、init()で人口が配置されているのです。マップリテラルはGoの初期化サイクルを作成し、コンパイルに失敗します。

    func init() { componentRegistry = mapstringComponentFunc{ "ColumnSplitter": renderColumnSplitter, "Image": renderImage, "Promo": renderPromo, "RichText": renderRichText, "Title": renderTitle, // Add entries here as you implement each component } }

ContainerとPartialDesignDynamicPlaceholderコンポーネントを実装してください

ContainerそしてPartialDesignDynamicPlaceholderは、ほとんどのSitecoreページで出会う構造的な要素です。 ColumnSplitterと同様に、フィールドコンテンツではなく入れ子状のプレースホルダースロットをレンダリングします。

  • Container
    要素でネストされたプレースホルダーの集合をラップし、オプションのCSSクラスパラメータ(StylesまたはCssClass)を受け入れます。コンテンツ作成者は、ページのセクションをグループ化し、スタイリングするために使います。
  • PartialDesignDynamicPlaceholder 部分的なデザインで使われる透過パススルーで、部分的なデザインはコンテンツ作成者がページビルダーで設定し、複数のページに共有する再利用可能なページセクションです。入れ子状のプレースホルダースロットをすべてレンダリングし、ラッパー要素は含めません。

ContainerおよびPartialDesignDynamicPlaceholderコンポーネントを実装するために:

::::タブズ:::tab{title="Astro"}

  1. Containerコンポーネントを作成する:

    --- // src/components/Container.astro import Placeholder from './Placeholder.astro';

    interface Props { params?: Record<string, any>; placeholders?: Record<string, any>; }

    const { params, placeholders } = Astro.props; const cssClass = params?.Styles ?? params?.CssClass ?? '';

    {Object.entries(placeholders ?? {}).map((name, items) => ( ))}
  2. PartialDesignDynamicPlaceholderコンポーネントを作成する:

    --- // src/components/PartialDesignDynamicPlaceholder.astro import Placeholder from './Placeholder.astro';

    interface Props { placeholders?: Record<string, any>; params?: Record<string, any>; }

    const { placeholders } = Astro.props;

    {Object.entries(placeholders ?? {}).map((name, items) => ( ))}

  3. 以下のインポートとエントリをsrc/components/componentMap.js更新して、両方のコンポーネントをコンポーネントマッピングに登録します。

    import Container from './Container.astro'; import PartialDesignDynamicPlaceholder from './PartialDesignDynamicPlaceholder.astro';

    export const componentMap = { // ...existing entries... 'Container': Container, 'PartialDesignDynamicPlaceholder': PartialDesignDynamicPlaceholder, };

  1. container.goを作成し、以下のコードを貼り付けます:

    // container.go package main

    import ( "fmt" "html/template" "strings" )

    func renderContainer(fields, params mapstringany, placeholders mapstringany) template.HTML { cssClass := ms(params, "Styles") if cssClass == "" { cssClass = ms(params, "CssClass") } classAttr := "" if cssClass != "" { classAttr = fmt.Sprintf(` class="%s"`, esc(cssClass)) } var sb strings.Builder sb.WriteString(fmt.Sprintf("<div%s>", classAttr)) for name, items := range placeholders { sb.WriteString(string(renderPlaceholder(name, items))) } sb.WriteString("") return template.HTML(sb.String()) }

    func renderPartialDesignDynamicPlaceholder(_ mapstringany, _ mapstringany, placeholders mapstringany) template.HTML { var sb strings.Builder for name, items := range placeholders { sb.WriteString(string(renderPlaceholder(name, items))) } return template.HTML(sb.String()) }

    !注renderContainerとrenderPartialDesignDynamicPlaceholderの両方がrenderPlaceholderを呼び出すため、componentRegistryを読み取るため、init() 関数(次のステップ参照)に登録され、写像リテラルには登録されません。マップリテラルに追加するとGoの初期化サイクルが発生し、コンパイルに失敗します。

  2. components.goでは、init()関数のcomponentRegistryにContainerとPartialDesignDynamicPlaceholderを加えます:

    func init() { componentRegistry = mapstringComponentFunc{ "ColumnSplitter": renderColumnSplitter, "Container": renderContainer, "Image": renderImage, "PartialDesignDynamicPlaceholder": renderPartialDesignDynamicPlaceholder, "Promo": renderPromo, "RichText": renderRichText, "Title": renderTitle, // Add entries here as you implement each component } }

ハンドル404ページ

サイト訪問者がSitecoreに存在しないパスに移動すると、レイアウトクエリはlayout.itemに対してnullを返します。前の手続きで設定したルーティングコードは、これを/404へのフォールバックとして扱っています。より完全な実装を望むなら、汎用パスにリダイレクトする代わりにSitecore管理の404ページをレンダリングできます。

404ページを扱うために:

  1. コンテンツ作成者がページビルダーで設定した notFoundPagePath と serverErrorPagePath を取得してください。これは site クエリの errorHandling フィールドを使うことでできます。
  2. fetchLayoutData関数を使って、前のステップで取得したパスのレイアウトデータを取得・レンダリングします。
  3. HTTP 404ステータスコードとともに応答を返し、ブラウザや検索エンジンが正しく「見つけられなかった応答」として扱うようにします。

アプリをテストしてください

今ではlocalhostでフロントエンドアプリをテストできます。

アプリのテスト:

  1. コードエディタにすべての変更を保存し、開発サーバーを再起動してください。
  2. localhostで / のルートに移動して ホームページ をレンダリングします。
  3. Sitecoreのコンテンツがレンダリングされているか確認してください。
  4. /productsやproducts/product-item-1などのネストルートをテストしてください。

次のステップ

これでフロントエンドアプリでSitecoreのコンテンツをレンダリングしました。あなたの申請:

  • ページデータのクエリはSitecoreに。
  • 再帰的なレイアウト構造を解析します。
  • レイアウトデータに基づいてコンポーネントをレンダリングします。
  • 入れ子状のコンポーネントやプレースホルダーを扱います。

次に、次のことができます:

  • Accordion、Header、Footer、カスタムコンポーネントなどのコンポーネント実装を引き続き構築・マッピングし、レイアウトデータのcomponentNameが正しくフィールドをレンダリングできるようにします。各コンポーネントを作る際には、フィールドタイプを必ず扱いましょう。
  • フロントエンドアプリはCSSやお好みのCSSフレームワークでスタイリングしましょう。
  • ナビゲーションメニュー、フッターリンク、リストデータなど、ページレイアウトに含まれないデータが必要なコンポーネントについては、item、search、またはsiteエントリーポイントを使って他のGraphQLクエリを行ってください。このデータをlayoutクエリとは別に取得し、その結果を関連するコンポーネントに渡します。ページごとのレイアウトからグローバルデータを排除することも、出版パフォーマンスの向上につながります
  • GraphQL APIについて、その違い、制限、ベストプラクティスについて詳しく学びましょう。
  • Sitecoreコンテンツのレンダリングに続く次の重要な開発タスクである ビジュアル編集を有効にしてください。
この記事を改善するための提案がある場合は、 お知らせください!