بنقرة واحدة
xm-cloud
Apply when working with Sitecore XM Cloud, JSS, Next.js rendering host, or Experience Edge
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Apply when working with Sitecore XM Cloud, JSS, Next.js rendering host, or Experience Edge
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
C# backend patterns for Optimizely CMS projects
Modern frontend patterns for headless Optimizely CMS (React, Next.js)
Razor view patterns for Optimizely CMS projects
Optimizely Content Cloud (DXP) and headless API patterns
Core Optimizely CMS development patterns and best practices
Modern frontend patterns for Optimizely Experimentation
| name | xm-cloud |
| description | Apply when working with Sitecore XM Cloud, JSS, Next.js rendering host, or Experience Edge |
| globs | ["**/src/rendering/**/*","**/xmcloud.build.json","**/*.graphql","**/src/components/**/*.tsx","**/package.json","**/*.tsx","**/*.jsx","**/tailwind.config.*"] |
This plugin includes the following additional skills for comprehensive development context:
| Skill | Purpose |
|---|---|
frontend-modern | React/Next.js component patterns, TypeScript, modern CSS |
fullstack-modern | GraphQL integration, SSR/SSG patterns, API routes |
These skills are automatically included when you install the XM Cloud Analyzer plugin.
┌─────────────────────────────────────────────────────────────┐
│ XM Cloud │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Content │───▶│ Experience │───▶│ Edge │ │
│ │ Management │ │ Edge │ │ CDN │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼ GraphQL
┌─────────────────────────────────────────────────────────────┐
│ Rendering Host │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Next.js │───▶│ SSG/ │───▶│ Vercel/ │ │
│ │ App │ │ ISR/SSR │ │ Netlify │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
src/
├── rendering/
│ ├── src/
│ │ ├── components/ # JSS components
│ │ │ ├── Hero/
│ │ │ │ ├── Hero.tsx
│ │ │ │ └── Hero.module.css
│ │ │ └── Navigation/
│ │ ├── lib/ # Utilities
│ │ │ ├── graphql/
│ │ │ ├── helpers/
│ │ │ └── component-props.ts
│ │ ├── graphql/ # GraphQL queries
│ │ │ ├── queries/
│ │ │ └── fragments/
│ │ ├── hooks/ # Custom React hooks
│ │ └── types/ # TypeScript definitions
│ ├── package.json
│ └── next.config.js
└── xmcloud.build.json
import {
Text,
Field,
RichText,
ImageField,
Image,
withDatasourceCheck,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';
interface HeroProps extends ComponentProps {
fields: {
heading: Field<string>;
subheading: Field<string>;
body: Field<string>;
image: ImageField;
ctaText: Field<string>;
ctaLink: LinkField;
};
}
const Hero = ({ fields }: HeroProps): JSX.Element => (
<section className="hero">
<div className="hero-content">
<Text field={fields.heading} tag="h1" />
<Text field={fields.subheading} tag="h2" />
<RichText field={fields.body} />
<Link field={fields.ctaLink}>
<Text field={fields.ctaText} />
</Link>
</div>
<div className="hero-image">
<Image field={fields.image} />
</div>
</section>
);
// Always wrap components that access datasource fields
export default withDatasourceCheck()<HeroProps>(Hero);
import {
Placeholder,
ComponentRendering,
} from '@sitecore-jss/sitecore-jss-nextjs';
interface LayoutProps {
rendering: ComponentRendering;
}
const TwoColumnLayout = ({ rendering }: LayoutProps): JSX.Element => (
<div className="two-column">
<aside className="sidebar">
<Placeholder name="sidebar" rendering={rendering} />
</aside>
<main className="main-content">
<Placeholder name="main" rendering={rendering} />
</main>
</div>
);
export default TwoColumnLayout;
import { GetStaticComponentProps } from '@sitecore-jss/sitecore-jss-nextjs';
import { fetchNavigationData } from 'lib/graphql/navigation';
interface NavigationProps {
fields: { ... };
navigationItems: NavigationItem[];
}
const Navigation = ({ fields, navigationItems }: NavigationProps) => (
<nav>
{navigationItems.map(item => (
<a key={item.id} href={item.url}>{item.title}</a>
))}
</nav>
);
// Fetch additional data at build time
export const getStaticProps: GetStaticComponentProps = async (rendering, layoutData) => {
const navigationItems = await fetchNavigationData(layoutData.sitecore.context.language);
return { navigationItems };
};
export default Navigation;
# fragments/NavigationFields.graphql
fragment NavigationFields on Item {
id
name
url {
path
}
navigationTitle: field(name: "Navigation Title") {
value
}
}
# queries/GetNavigation.graphql
query GetNavigation($rootPath: String!, $language: String!) {
item(path: $rootPath, language: $language) {
children(hasLayout: true) {
...NavigationFields
children {
...NavigationFields
}
}
}
}
query SearchArticles(
$language: String!
$category: String
$first: Int = 10
$after: String
) {
search(
where: {
AND: [
{ name: "_language", value: $language }
{ name: "_templatename", value: "Article" }
{ name: "category", value: $category }
]
}
orderBy: { name: "date", direction: DESC }
first: $first
after: $after
) {
pageInfo {
hasNext
endCursor
}
results {
... on Article {
id
title { value }
summary { value }
date { value }
image {
src
alt
}
}
}
}
}
// lib/graphql/client.ts
import { GraphQLClient } from 'graphql-request';
const endpoint = process.env.GRAPH_QL_ENDPOINT!;
const apiKey = process.env.SITECORE_API_KEY!;
export const graphqlClient = new GraphQLClient(endpoint, {
headers: {
'sc_apikey': apiKey,
},
});
// lib/graphql/navigation.ts
import { graphqlClient } from './client';
import { GetNavigationDocument } from 'graphql/generated';
export async function fetchNavigationData(language: string) {
try {
const data = await graphqlClient.request(GetNavigationDocument, {
rootPath: '/sitecore/content/site/home',
language,
});
return data.item?.children ?? [];
} catch (error) {
console.error('Failed to fetch navigation:', error);
return [];
}
}
// Best for: Blog posts, marketing pages, documentation
export async function getStaticProps(context: GetStaticPropsContext) {
const props = await sitecorePagePropsFactory.create(context);
return {
props,
revalidate: 300, // ISR: regenerate every 5 minutes
};
}
export async function getStaticPaths() {
return {
paths: [], // Generate on-demand
fallback: 'blocking',
};
}
// Use for: Personalized content, real-time data
export async function getServerSideProps(context: GetServerSidePropsContext) {
const props = await sitecorePagePropsFactory.create(context);
// Set cache headers for Edge
context.res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
);
return { props };
}
// Use for: User-specific data, frequently changing content
import useSWR from 'swr';
function UserDashboard() {
const { data, error, isLoading } = useSWR('/api/user/dashboard', fetcher, {
revalidateOnFocus: true,
refreshInterval: 30000,
});
if (isLoading) return <LoadingSkeleton />;
if (error) return <ErrorMessage />;
return <Dashboard data={data} />;
}
# .env.local (never commit)
SITECORE_API_KEY=your-api-key-here
SITECORE_API_HOST=https://cm.your-instance.sitecorecloud.io
GRAPH_QL_ENDPOINT=https://edge.sitecorecloud.io/api/graphql/v1
JSS_EDITING_SECRET=your-editing-secret
# Public variables (safe to expose)
NEXT_PUBLIC_SITE_NAME=your-site
NEXT_PUBLIC_DEFAULT_LANGUAGE=en
NEXT_PUBLIC_PUBLIC_URL=https://your-site.com
// lib/config.ts
const requiredServerEnvs = [
'SITECORE_API_KEY',
'GRAPH_QL_ENDPOINT',
'JSS_EDITING_SECRET',
] as const;
const requiredPublicEnvs = [
'NEXT_PUBLIC_SITE_NAME',
] as const;
// Validate at startup
function validateEnv() {
const missing: string[] = [];
requiredServerEnvs.forEach(key => {
if (!process.env[key]) missing.push(key);
});
requiredPublicEnvs.forEach(key => {
if (!process.env[key]) missing.push(key);
});
if (missing.length > 0) {
throw new Error(`Missing required env vars: ${missing.join(', ')}`);
}
}
validateEnv();
export const config = {
sitecoreApiKey: process.env.SITECORE_API_KEY!,
graphqlEndpoint: process.env.GRAPH_QL_ENDPOINT!,
siteName: process.env.NEXT_PUBLIC_SITE_NAME!,
};
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import {
PersonalizeMiddleware,
RedirectsMiddleware,
MultisiteMiddleware,
} from '@sitecore-jss/sitecore-jss-nextjs/middleware';
const personalizeMiddleware = new PersonalizeMiddleware({
// Personalization configuration
edgeConfig: {
endpoint: process.env.GRAPH_QL_ENDPOINT!,
apiKey: process.env.SITECORE_API_KEY!,
},
// CDP configuration if using
cdpConfig: {
endpoint: process.env.NEXT_PUBLIC_CDP_TARGET_URL,
clientKey: process.env.NEXT_PUBLIC_CDP_CLIENT_KEY,
},
});
const redirectsMiddleware = new RedirectsMiddleware({
// Redirects configuration
});
export async function middleware(request: NextRequest) {
// Run middleware chain
let response = await personalizeMiddleware.getHandler()(request);
if (response) return response;
response = await redirectsMiddleware.getHandler()(request);
if (response) return response;
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
// For Sitecore-managed images (supports Experience Editor)
import { Image as JssImage } from '@sitecore-jss/sitecore-jss-nextjs';
<JssImage
field={fields.image}
width={800}
height={600}
/>
// For static images with Next.js optimization
import NextImage from 'next/image';
<NextImage
src="/images/hero.jpg"
alt="Hero"
width={800}
height={600}
priority // For above-the-fold
placeholder="blur"
blurDataURL={blurDataUrl}
/>
// Responsive Sitecore image
<JssImage
field={fields.image}
srcSet={[{ mw: 300 }, { mw: 600 }, { mw: 1200 }]}
sizes="(max-width: 600px) 100vw, 50vw"
/>
// components/ErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Component error:', error, errorInfo);
// Send to error tracking service
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-container">
<h2>Something went wrong</h2>
<p>Please try refreshing the page</p>
</div>
);
}
return this.props.children;
}
}
export async function fetchWithErrorHandling<T>(
query: string,
variables: Record<string, unknown>
): Promise<T | null> {
try {
const response = await graphqlClient.request<T>(query, variables);
return response;
} catch (error) {
if (error instanceof ClientError) {
console.error('GraphQL errors:', error.response.errors);
// Handle specific error types
if (error.response.status === 401) {
// Handle auth error
}
}
console.error('Query failed:', error);
return null;
}
}
// hooks/useNavigation.ts
import { useQuery } from '@tanstack/react-query';
import { fetchNavigationData } from 'lib/graphql/navigation';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
export function useNavigation() {
const { sitecoreContext } = useSitecoreContext();
const language = sitecoreContext.language || 'en';
return useQuery({
queryKey: ['navigation', language],
queryFn: () => fetchNavigationData(language),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
});
}
// hooks/usePageContext.ts
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
export function usePageContext() {
const { sitecoreContext } = useSitecoreContext();
return {
language: sitecoreContext.language || 'en',
isEditing: sitecoreContext.pageEditing,
route: sitecoreContext.route,
site: sitecoreContext.site,
};
}
// types/sitecore.ts
import { Field, ImageField, LinkField } from '@sitecore-jss/sitecore-jss-nextjs';
export interface ArticleFields {
title: Field<string>;
summary: Field<string>;
body: Field<string>;
image: ImageField;
author: Field<string>;
date: Field<string>;
category: Field<string>;
}
export interface NavigationItem {
id: string;
name: string;
url: string;
title: string;
children?: NavigationItem[];
}
export interface SearchResult<T> {
results: T[];
pageInfo: {
hasNext: boolean;
endCursor: string;
};
total: number;
}