Created
November 10, 2025 11:37
-
-
Save jbreuer/9ee71eacf6b58f27153b814bf61a1f84 to your computer and use it in GitHub Desktop.
Testing the Sitecore Content SDK App Router beta
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { NextResponse } from 'next/server'; | |
| import { NextRequest } from 'next/server'; | |
| /** | |
| * Product Availability API | |
| * | |
| * Simulates checking inventory/stock from a slow backend system. | |
| * Represents real-world scenarios like checking warehouse inventory, | |
| * fetching real-time pricing, or validating product availability. | |
| */ | |
| export async function GET(request: NextRequest) { | |
| // Simulate slow external API (2-second delay for demo) | |
| await new Promise((resolve) => setTimeout(resolve, 2000)); | |
| // Random stock level (simulates real-time inventory check) | |
| const now = new Date(); | |
| const time = now.toLocaleTimeString('en-US', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| }); | |
| const ms = now.getMilliseconds().toString().padStart(3, '0'); | |
| const data = { | |
| quantity: Math.floor(Math.random() * 50) + 10, // Random 10-60 | |
| lastUpdated: time.replace(' AM', `:${ms} AM`).replace(' PM', `:${ms} PM`), | |
| }; | |
| return NextResponse.json(data); | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { JSX, Suspense } from 'react'; | |
| import { | |
| NextImage as ContentSdkImage, | |
| Link as ContentSdkLink, | |
| RichText as ContentSdkRichText, | |
| ImageField, | |
| Field, | |
| LinkField, | |
| } from '@sitecore-content-sdk/nextjs'; | |
| import { ComponentProps } from 'lib/component-props'; | |
| /** | |
| * Async Server Component - Fetches data server-side | |
| * This demonstrates async/await in Server Components | |
| */ | |
| async function AsyncPromoData({ productId }: { productId: number }) { | |
| // Fetch product availability | |
| const response = await fetch(`http://localhost:3000/api/products/availability?id=${productId}`, { | |
| cache: 'no-store', | |
| }); | |
| const data = await response.json(); | |
| return ( | |
| <div style={{ marginTop: '0.5rem', padding: '0.5rem', background: '#d4edda', borderRadius: '4px' }}> | |
| <strong>In Stock:</strong> {data.quantity} items • Updated: {data.lastUpdated} | |
| </div> | |
| ); | |
| } | |
| interface Fields { | |
| PromoIcon: ImageField; | |
| PromoText: Field<string>; | |
| PromoLink: LinkField; | |
| PromoText2: Field<string>; | |
| } | |
| type PromoProps = ComponentProps & { | |
| fields: Fields; | |
| }; | |
| interface PromoContentProps extends PromoProps { | |
| renderText: (fields: Fields) => JSX.Element; | |
| } | |
| const PromoContent = (props: PromoContentProps): JSX.Element => { | |
| const { fields, params, renderText } = props; | |
| const { styles, RenderingIdentifier: id } = params; | |
| const Wrapper = ({ children }: { children: JSX.Element }): JSX.Element => ( | |
| <div className={`component promo ${styles}`} id={id}> | |
| <div className="component-content">{children}</div> | |
| </div> | |
| ); | |
| if (!fields) { | |
| return ( | |
| <Wrapper> | |
| <span className="is-empty-hint">Promo</span> | |
| </Wrapper> | |
| ); | |
| } | |
| return ( | |
| <Wrapper> | |
| <> | |
| <div className="field-promoicon"> | |
| <ContentSdkImage field={fields.PromoIcon} /> | |
| </div> | |
| <div className="promo-text">{renderText(fields)}</div> | |
| </> | |
| </Wrapper> | |
| ); | |
| }; | |
| export const Default = async (props: PromoProps): Promise<JSX.Element> => { | |
| // Delay to make streaming visible (remove in production) | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| const renderText = (fields: Fields) => ( | |
| <> | |
| <div className="field-promotext"> | |
| <ContentSdkRichText field={fields.PromoText} /> | |
| </div> | |
| <div className="field-promolink"> | |
| <ContentSdkLink field={fields.PromoLink} /> | |
| </div> | |
| {/* Nested Suspense for async data streaming */} | |
| <Suspense fallback={<p style={{ marginTop: '0.5rem', fontStyle: 'italic' }}>Loading...</p>}> | |
| <AsyncPromoData productId={Math.random()} /> | |
| </Suspense> | |
| </> | |
| ); | |
| return <PromoContent {...props} renderText={renderText} />; | |
| }; | |
| export const WithText = (props: PromoProps): JSX.Element => { | |
| const renderText = (fields: Fields) => ( | |
| <> | |
| <div className="field-promotext"> | |
| <ContentSdkRichText className="promo-text" field={fields.PromoText} /> | |
| </div> | |
| <div className="field-promotext"> | |
| <ContentSdkRichText className="promo-text" field={fields.PromoText2} /> | |
| </div> | |
| </> | |
| ); | |
| return <PromoContent {...props} renderText={renderText} />; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { type NextRequest, type NextFetchEvent } from 'next/server'; | |
| import { | |
| defineMiddleware, | |
| AppRouterMultisiteMiddleware, | |
| PersonalizeMiddleware, | |
| RedirectsMiddleware, | |
| LocaleMiddleware, | |
| } from '@sitecore-content-sdk/nextjs/middleware'; | |
| import sites from '.sitecore/sites.json'; | |
| import scConfig from 'sitecore.config'; | |
| import { routing } from './i18n/routing'; | |
| // Determine if we're using Edge mode or local container mode | |
| const isEdgeMode = !!( | |
| scConfig.api.edge?.contextId || | |
| scConfig.api.edge?.clientContextId | |
| ); | |
| const locale = new LocaleMiddleware({ | |
| /** | |
| * List of sites for site resolver to work with | |
| */ | |
| sites, | |
| /** | |
| * List of all supported locales configured in routing.ts | |
| */ | |
| locales: routing.locales.slice(), | |
| // This function determines if the middleware should be turned off on per-request basis. | |
| // Certain paths are ignored by default (e.g. files and Next.js API routes), but you may wish to disable more. | |
| // This is an important performance consideration since Next.js Edge middleware runs on every request. | |
| // in multilanguage scenarios, we need locale middleware to always run first to ensure locale is set and used correctly by the rest of the middlewares | |
| skip: () => false, | |
| }); | |
| // Edge-dependent middlewares: only create when using XM Cloud Edge | |
| // These middlewares are not compatible with local container development | |
| const middlewareChain = isEdgeMode | |
| ? [ | |
| locale, | |
| new AppRouterMultisiteMiddleware({ | |
| sites, | |
| ...scConfig.api.edge, | |
| ...scConfig.multisite, | |
| skip: () => false, | |
| }), | |
| new RedirectsMiddleware({ | |
| sites, | |
| ...scConfig.api.edge, | |
| ...scConfig.redirects, | |
| skip: () => false, | |
| }), | |
| new PersonalizeMiddleware({ | |
| sites, | |
| ...scConfig.api.edge, | |
| ...scConfig.personalize, | |
| skip: () => false, | |
| }), | |
| ] | |
| : [locale]; // Local container mode: only use locale middleware | |
| export function middleware(req: NextRequest, ev: NextFetchEvent) { | |
| return defineMiddleware(...middlewareChain).exec(req, ev); | |
| } | |
| export const config = { | |
| /* | |
| * Match all paths except for: | |
| * 1. API route handlers | |
| * 2. /_next (Next.js internals) | |
| * 3. /sitecore/api (Sitecore API routes) | |
| * 4. /- (Sitecore media) | |
| * 5. /healthz (Health check) | |
| * 7. all root files inside /public | |
| */ | |
| matcher: [ | |
| '/', | |
| '/((?!api/|sitemap|robots|_next/|healthz|sitecore/api/|-/|favicon.ico|sc_logo.svg).*)', | |
| ], | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment