Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/slow-lamps-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@sitecore-content-sdk/nextjs": minor
"create-content-sdk-app": minor
---

Add tag-based revalidation support for App Router OSR, including cache tag helpers and revalidation route handlers.

Introduce the `nextjs-app-router-osr` scaffolding template with manual and webhook revalidation routes wired out of the box.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { BaseAppArgs } from '../../common';
import { NextjsAppRouterAnswer } from '../nextjs-app-router/prompts';

export type NextjsAppRouterOsrArgs = BaseAppArgs & Partial<NextjsAppRouterAnswer>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import path from 'path';
import inquirer from 'inquirer';
import { prompts, NextjsAppRouterAnswer } from '../nextjs-app-router/prompts';
import { Initializer, transform } from '../../common';
import { NextjsAppRouterOsrArgs } from './args';

export default class NextjsAppRouterOsrInitializer implements Initializer {
async init(args: NextjsAppRouterOsrArgs) {
const answers = await inquirer.prompt<NextjsAppRouterAnswer>(prompts, args);
const templatePath = path.resolve(__dirname, '../../templates/nextjs-app-router-osr');

await transform(templatePath, { ...args, ...answers });

return {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This file should be copied to .env.local and modified with your environment variables to connect to your Sitecore container instance.

# To secure the Sitecore editor endpoint exposed by your Next.js app
# (`/api/editing/render` by default), a secret token is used. This (client-side)
SITECORE_EDITING_SECRET=

# Secret token for protected cache tag revalidation endpoint (`/api/revalidate`).
# Send this value in the `x-revalidate-secret` header.
SITECORE_REVALIDATE_SECRET=

# Your Sitecore site name.
# The value of the variable represents the default/configured site.
NEXT_PUBLIC_DEFAULT_SITE_NAME=

# Your default app language.
NEXT_PUBLIC_DEFAULT_LANGUAGE=

# Your Sitecore API key is needed to build the app.
NEXT_PUBLIC_SITECORE_API_KEY=

# Your Sitecore API hostname is needed to build the app.
NEXT_PUBLIC_SITECORE_API_HOST=

# Sitecore Content SDK npm packages utilize the debug module for debug logging.
# https://www.npmjs.com/package/debug
# Set the DEBUG environment variable to 'content-sdk:*' to see all logs:
#DEBUG=content-sdk:*
# Or be selective and show for example only layout service logs:
#DEBUG=content-sdk:layout
# Or everything BUT layout service logs:
#DEBUG=content-sdk:*,-content-sdk:layout
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This file should be copied to .env.local and modified with your environment variables to connect to your remote Sitecore instance.

# To secure the Sitecore editor endpoint exposed by your Next.js app
# (`/api/editing/render` by default), a secret token is used. This (client-side)
SITECORE_EDITING_SECRET=

# Secret token for protected cache tag revalidation endpoint (`/api/revalidate`).
# Send this value in the `x-revalidate-secret` header.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do customers need to be aware of these details and specific headers? It looks like the webhook always uses the same format and passes the same header, customers will not do that manually

SITECORE_REVALIDATE_SECRET=

# Your Sitecore site name.
# The value of the variable represents the default/configured site.
NEXT_PUBLIC_DEFAULT_SITE_NAME=

# Your default app language.
NEXT_PUBLIC_DEFAULT_LANGUAGE=

# Your unified Sitecore Edge Context Id for server-side use.
# This will be used over any Sitecore Preview / Delivery Edge variables (above).
SITECORE_EDGE_CONTEXT_ID=

# Your Sitecore Edge Context Id for client-side use.
# Will be used as a fallback if separate SITECORE_EDGE_CONTEXT_ID value is not provided
NEXT_PUBLIC_SITECORE_EDGE_CONTEXT_ID=

# Optional: custom Sitecore Edge Platform hostname (hostname or full URL).
NEXT_PUBLIC_SITECORE_EDGE_PLATFORM_HOSTNAME=

# Optional: custom Experience Edge hostname for media URL rewriting (e.g. staging).
SITECORE_EXPERIENCE_EDGE_HOSTNAME=

# An optional Sitecore Personalize scope identifier.
# This can be used to isolate personalization data when multiple XM Cloud Environments share a Personalize tenant.
# This should match the PAGES_PERSONALIZE_SCOPE environment variable for your connected XM Cloud Environment.
NEXT_PUBLIC_PERSONALIZE_SCOPE=

# Timeout (ms) for Sitecore CDP requests to respond within. Default is 400.
PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT=

# Timeout (ms) for Sitecore Experience Edge requests to respond within. Default is 400.
PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT=

# Sitecore Content SDK npm packages utilize the debug module for debug logging.
# https://www.npmjs.com/package/debug
# Set the DEBUG environment variable to 'content-sdk:*' to see all logs:
#DEBUG=content-sdk:*
# Or be selective and show for example only layout service logs:
#DEBUG=content-sdk:layout
# Or everything BUT layout service logs:
#DEBUG=content-sdk:*,-content-sdk:layout

# Client ID, Secret and Rendering Host Name used for Design Library functionality
SITECORE_AUTH_CLIENT_ID=
SITECORE_AUTH_CLIENT_SECRET=
SITECORE_RENDERINGHOST_NAME=
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Line endings for this repository
# See: https://help.github.com/en/articles/configuring-git-to-handle-line-endings
# This should line up with the expectations from .eslintrc

# Set the default behavior, in case people don't have core.autocrlf set.
* text=crlf

# Declare files that will always have CRLF line endings on checkout.
*.ts text eol=crlf
*.tsx text eol=crlf
*.js text eol=crlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Client-safe component map for App Router
import {
BYOCClientWrapper,
NextjsContentSdkComponent,
FEaaSClientWrapper,
} from '@sitecore-content-sdk/nextjs';
import { Form } from '@sitecore-content-sdk/nextjs';

export const componentMap = new Map<string, NextjsContentSdkComponent>([
['BYOCWrapper', BYOCClientWrapper],
['FEaaSWrapper', FEaaSClientWrapper],
['Form', Form],
]);

export default componentMap;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Below are built-in components that are available in the app, it's recommended to keep them as is
import { BYOCWrapper, NextjsContentSdkComponent, FEaaSWrapper } from '@sitecore-content-sdk/nextjs';
import { Form } from '@sitecore-content-sdk/nextjs';
// end of built-in components

// Components must be registered within the map to match the string key with component name in Sitecore
export const componentMap = new Map<string, NextjsContentSdkComponent>([
['BYOCWrapper', BYOCWrapper],
['FEaaSWrapper', FEaaSWrapper],
['Form', { ...Form, componentType: 'client' }],
]);

export default componentMap;

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file is auto-generated by the Sitecore Content SDK.
// Below are built-in Content SDK imports neccessary for the import map
import {
combineImportEntries,
defaultImportEntries,
ImportEntry,
} from '@sitecore-content-sdk/nextjs/codegen';
// end of built-in imports

const importMap: ImportEntry[] = [];

export default combineImportEntries(defaultImportEntries, importMap);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// This file is auto-generated by the Sitecore Content SDK.
// Below are built-in Content SDK imports neccessary for the import map
import { combineImportEntries, defaultImportEntries } from '@sitecore-content-sdk/nextjs/codegen';
// end of built-in imports

import {
Link,
Text,
RichText,
NextImage,
Placeholder as Placeholder_8a80e63291fea86e0744df19113dc44bec187216,
AppPlaceholder,
CdpHelper,
} from '@sitecore-content-sdk/nextjs';
import { Suspense } from 'react';
import React from 'react';
import { componentMap } from '.sitecore/component-map';
import client from 'src/lib/sitecore-client';
import { pageView } from '@sitecore-content-sdk/events';
import config from 'sitecore.config';

const importMapServer = [
{
module: '@sitecore-content-sdk/nextjs',
exports: [
{ name: 'Link', value: Link },
{ name: 'Text', value: Text },
{ name: 'RichText', value: RichText },
{ name: 'NextImage', value: NextImage },
{ name: 'Placeholder', value: Placeholder_8a80e63291fea86e0744df19113dc44bec187216 },
{ name: 'AppPlaceholder', value: AppPlaceholder },
{ name: 'CdpHelper', value: CdpHelper },
],
},
{
module: 'react',
exports: [
{ name: 'Suspense', value: Suspense },
{ name: 'default', value: React },
],
},
{
module: '.sitecore/component-map',
exports: [{ name: 'componentMap', value: componentMap }],
},
{
module: 'src/lib/sitecore-client',
exports: [{ name: 'default', value: client }],
},
{
module: '@sitecore-content-sdk/events',
exports: [{ name: 'pageView', value: pageView }],
},
{
module: 'sitecore.config',
exports: [{ name: 'default', value: config }],
},
];

export default combineImportEntries(defaultImportEntries, importMapServer);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Sitecore Content SDK Next.js App Router — On-demand revalidation (OSR)

This starter matches the default **nextjs-app-router** template, with **tag-based on-demand revalidation** enabled: Next.js Cache Components (`cacheComponents`), `getSitecorePage` / `getSitecoreDictionary` helpers that apply Sitecore cache tags, and **`/api/revalidate`** plus **`/api/revalidate/webhook`** route handlers. From the app root you can call those URLs using standard HTTP tooling (see `docs/tag-based-revalidation.md` and the monorepo **`docs/tag-based-revalidation.md`** for the full walkthrough).

Use the **`nextjs-app-router`** template if you do not need OSR wiring.

[SitecoreAI Content SDK Documentation](https://doc.sitecore.com/sai/en/developers/content-sdk/sitecore-content-sdk-for-sitecoreai.html)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should start adding documentation directly into our starters, since it’s easy for it to become outdated and it introduces additional maintenance overhead. However, we definitely should document this properly on the Sitecore documentation side

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Tag-based OSR in this app

Step-by-step guidance and customization live in the Content SDK repository:

**[docs/tag-based-revalidation.md](https://github.com/Sitecore/content-sdk/blob/dev/docs/tag-based-revalidation.md)** (source path in the monorepo: `docs/tag-based-revalidation.md`).

## Quick map

| Piece | Path |
|-------|------|
| Cached page + tags | `src/lib/cache/get-sitecore-page.ts` |
| Cached dictionary + tag | `src/lib/cache/get-sitecore-dictionary.ts` |
| Manual `POST` | `src/app/api/revalidate/route.ts` |
| Webhook `POST` | `src/app/api/revalidate/webhook/route.ts` |

Set **`SITECORE_REVALIDATE_SECRET`** and call the revalidate routes with standard HTTP tooling from the app root (see the main doc).
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

export default defineConfig([
...nextVitals,
...nextTs,
{
rules: {
// Don't force alt for <Image/> (sourced from Sitecore media)
"jsx-a11y/alt-text": "off",
},
},
globalIgnores([
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# next.js
/.next*/
/out/

# misc
.DS_Store

# local env files
.env.local
.env.*.local
.env

# Log files
*.log*

# vercel
.vercel

# sitecore temp files
.sitecore/*
# except for component-map
!.sitecore/component-map.ts
!.sitecore/component-map.client.ts
!.sitecore/import-map.ts
!.sitecore/import-map.server.ts
!.sitecore/import-map.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
import { imageRemotePatterns } from './src/lib/image-remote-patterns';

const allowedDevOrigins = (process.env.NEXT_ALLOWED_DEV_ORIGINS ?? '')
.split(',')
.map((origin) => origin.trim())
.filter(Boolean);

const nextConfig: NextConfig = {
// Enable Cache Components (`use cache`, `cacheTag`) in Next.js App Router.
cacheComponents: true,
...(allowedDevOrigins.length > 0 ? { allowedDevOrigins } : {}),

// Enable Turbopack file system caching for faster dev startup (beta)
// See: https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack
experimental: {
turbopackFileSystemCacheForDev: true,
},

// use this configuration to ensure that only images from the whitelisted domains
// can be served from the Next.js Image Optimization API
// see https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
images: {
remotePatterns: imageRemotePatterns,
},
// use this configuration to serve the sitemap.xml and robots.txt files from the API route handlers
rewrites: async () => {
return [
{
source: '/sitemap:id([\\w-]{0,}).xml',
destination: '/api/sitemap',
locale: false,
},
{
source: '/robots.txt',
destination: '/api/robots',
locale: false,
},
];
},
};

const withNextIntl = createNextIntlPlugin();

export default withNextIntl(nextConfig);
Loading
Loading