From de4d28f80b861e51af1beb24ee1df505ec6c9b54 Mon Sep 17 00:00:00 2001 From: Muhammad Faiq Amin Bin Abd Razak Date: Tue, 26 Aug 2025 00:46:02 +0800 Subject: [PATCH 1/3] Enhance Next.js configuration for Editing Host support - Added environment variable for NEXT_PUBLIC_EDITING_HOST to enable build-time dead-code elimination. - Updated API routes to conditionally handle requests based on the editing host environment. - Modified Layout component to load import map only when in editing mode. - Adjusted Sitecore CLI configuration to disable code generation when editing host is active. --- .../src/templates/nextjs/next.config.js | 19 +++++++++++---- .../templates/nextjs/sitecore.cli.config.ts | 12 ++++++---- .../src/templates/nextjs/sitecore.config.ts | 4 +++- .../src/templates/nextjs/src/Layout.tsx | 24 ++++++++++++++----- .../src/pages/api/editing/feaas/render.ts | 11 +++++++-- .../nextjs/src/pages/api/editing/render.ts | 11 +++++++-- 6 files changed, 61 insertions(+), 20 deletions(-) diff --git a/packages/create-content-sdk-app/src/templates/nextjs/next.config.js b/packages/create-content-sdk-app/src/templates/nextjs/next.config.js index 0951dad4bc..688d53d819 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/next.config.js +++ b/packages/create-content-sdk-app/src/templates/nextjs/next.config.js @@ -22,6 +22,10 @@ const nextConfig = { // Disable the X-Powered-By header. Follows security best practices. poweredByHeader: false, + // Expose the editing host flag for build-time dead-code elimination + env: { + NEXT_PUBLIC_EDITING_HOST: process.env.NEXT_PUBLIC_EDITING_HOST || 'false', + }, // use this configuration to ensure that only images from the whitelisted domains // can be served from the Next.js Image Optimization API @@ -42,7 +46,7 @@ const nextConfig = { }, async rewrites() { - return [ + const routes = [ // healthz check { source: '/healthz', @@ -58,12 +62,17 @@ const nextConfig = { source: '/sitemap:id([\\w-]{0,}).xml', destination: '/api/sitemap' }, - // feaas api route - { + ]; + + // Only add FEAAS editing route for Editing Host builds + if (process.env.NEXT_PUBLIC_EDITING_HOST === 'true') { + routes.push({ source: '/feaas-render', destination: '/api/editing/feaas/render', - }, - ]; + }); + } + + return routes; }, webpack: (config, options) => { diff --git a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.cli.config.ts b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.cli.config.ts index 4107dcab1a..6cd43c13c9 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.cli.config.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.cli.config.ts @@ -17,10 +17,14 @@ export default defineCliConfig({ extractFiles({ scConfig, }), - writeImportMap({ - paths: ['src/components'], - scConfig, - }), + ...(scConfig.disableCodeGeneration + ? [] + : [ + writeImportMap({ + paths: ['src/components'], + scConfig, + }), + ]), ], }, componentMap: { diff --git a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts index 24de77be3d..1e06c069bc 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts @@ -4,4 +4,6 @@ import { defineConfig } from '@sitecore-content-sdk/nextjs/config'; * See the documentation for `defineConfig`: * https://doc.sitecore.com/xmc/en/developers/content-sdk/the-sitecore-configuration-file.html */ -export default defineConfig({}); +export default defineConfig({ + disableCodeGeneration: process.env.NEXT_PUBLIC_EDITING_HOST !== 'true', +}); diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx b/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx index af71021024..25db19008e 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx @@ -1,12 +1,12 @@ -/** +// @ts-nocheck +/** * This Layout is needed for Starter Kit. */ import React, { JSX } from 'react'; import Head from 'next/head'; -import { Placeholder, Field, DesignLibrary, Page } from '@sitecore-content-sdk/nextjs'; +import { Placeholder, Field, Page, DesignLibrary } from '@sitecore-content-sdk/nextjs'; import Scripts from 'src/Scripts'; import SitecoreStyles from 'src/components/content-sdk/SitecoreStyles'; - interface LayoutProps { page: Page; } @@ -21,7 +21,19 @@ const Layout = ({ page }: LayoutProps): JSX.Element => { const { route } = layout.sitecore; const fields = route?.fields as RouteFields; const mainClassPageEditing = mode.isEditing ? 'editing-mode' : 'prod-mode'; - const importMapDynamic = () => import('.sitecore/import-map'); + + const isEditingHost = process.env.NEXT_PUBLIC_EDITING_HOST === 'true'; + let importMap: any = undefined; + if (isEditingHost) { + try { + importMap = require('.sitecore/import-map').default; + } catch (error) { + console.warn( + 'DesignLibrary: failed to load .sitecore/import-map; variant generation may be limited.', + error + ); + } + } return ( <> @@ -34,8 +46,8 @@ const Layout = ({ page }: LayoutProps): JSX.Element => { {/* root placeholder for the app, which we add components to using route data */}
- {mode.isDesignLibrary ? ( - + {mode.isDesignLibrary && isEditingHost ? ( + ) : ( <>
diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts index 66c50fffbe..32d2fe610d 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts @@ -1,4 +1,5 @@ import { FEAASRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; +import type { NextApiRequest, NextApiResponse } from 'next'; /** * This Next.js API route is used to handle GET requests from Sitecore Component Builder. @@ -11,6 +12,12 @@ */ // Wire up the FEAASRenderMiddleware handler -const handler = new FEAASRenderMiddleware().getHandler(); +const baseHandler = new FEAASRenderMiddleware().getHandler(); -export default handler; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + if (process.env.NEXT_PUBLIC_EDITING_HOST !== 'true') { + res.status(404).end(); + return; + } + return baseHandler(req, res); +} diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts index 85e3cc93de..10b94d1a78 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts @@ -1,4 +1,5 @@ import { EditingRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; +import type { NextApiRequest, NextApiResponse } from 'next'; /** * This Next.js API route is used to handle GET requests from Sitecore Editor. @@ -23,6 +24,12 @@ export const config = { }; // Wire up the EditingRenderMiddleware handler -const handler = new EditingRenderMiddleware().getHandler(); +const baseHandler = new EditingRenderMiddleware().getHandler(); -export default handler; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + if (process.env.NEXT_PUBLIC_EDITING_HOST !== 'true') { + res.status(404).end(); + return; + } + return baseHandler(req, res); +} From 3fcbe063e9d7cb149ff5191c95948d0a434513a4 Mon Sep 17 00:00:00 2001 From: Muhammad Faiq Amin Bin Abd Razak Date: Tue, 26 Aug 2025 16:20:42 +0800 Subject: [PATCH 2/3] Fix type assertion in API handler for Next.js editing route --- .../src/templates/nextjs/src/pages/api/editing/render.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts index 10b94d1a78..7165adfc9c 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts @@ -31,5 +31,5 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) { res.status(404).end(); return; } - return baseHandler(req, res); + return baseHandler(req as any, res as any); } From 4f0280d63b828bb033cd17ac3b420165c4f1986f Mon Sep 17 00:00:00 2001 From: Muhammad Faiq Amin Bin Abd Razak Date: Wed, 27 Aug 2025 14:44:26 +0800 Subject: [PATCH 3/3] Enhance Editing Host support in Next.js configuration - Introduced `isEditingHost` flag derived from the `SITECORE_EDITING_SECRET` environment variable for consistent handling across components. - Updated `next.config.js` to conditionally remove editing API routes for Rendering Hosts. - Modified `Layout` component to utilize the new `isEditingHost` flag for loading import maps. - Added new API routes for handling requests from Sitecore Editor and Component Builder. - Adjusted Sitecore configuration to propagate the editing host flag to the Next.js layer. --- packages/core/src/config/define-config.ts | 2 + packages/core/src/config/models.ts | 6 ++ .../src/templates/nextjs/next.config.js | 58 +++++++++++++++++-- .../src/templates/nextjs/sitecore.config.ts | 8 ++- .../src/templates/nextjs/src/Layout.tsx | 6 +- .../feaas/{render.ts => render.editing.ts} | 6 +- .../editing/{render.ts => render.editing.ts} | 7 +-- packages/nextjs/src/config/define-config.ts | 2 + 8 files changed, 76 insertions(+), 19 deletions(-) rename packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/{render.ts => render.editing.ts} (78%) rename packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/{render.ts => render.editing.ts} (84%) diff --git a/packages/core/src/config/define-config.ts b/packages/core/src/config/define-config.ts index fc5e311d5b..8cab75ff4e 100644 --- a/packages/core/src/config/define-config.ts +++ b/packages/core/src/config/define-config.ts @@ -6,6 +6,7 @@ import { DeepPartial, SitecoreConfig, SitecoreConfigInput } from './models'; * Provides default initial values for SitecoreConfig * @returns default config */ + export const getFallbackConfig = (): SitecoreConfig => ({ api: { edge: { @@ -20,6 +21,7 @@ export const getFallbackConfig = (): SitecoreConfig => ({ }, }, editingSecret: process.env.SITECORE_EDITING_SECRET || 'editing-secret-missing', + isEditingHost: !!process.env.SITECORE_EDITING_SECRET, retries: { count: 3, retryStrategy: new DefaultRetryStrategy({ diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 164e8b086d..5838bbfeb0 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -79,6 +79,12 @@ export type SitecoreConfigInput = { * Default comes from the SITECORE_EDITING_SECRET environment variable. */ editingSecret?: string; + /** + * Indicates whether the app is running as an Editing Host. + * Recommended to be derived from the presence of `editingSecret`. + * Defaults to `!!process.env.SITECORE_EDITING_SECRET`. + */ + isEditingHost?: boolean; /** * Retry configuration applied to Layout, Dictionary and ErrorPages services */ diff --git a/packages/create-content-sdk-app/src/templates/nextjs/next.config.js b/packages/create-content-sdk-app/src/templates/nextjs/next.config.js index 688d53d819..7653145b05 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/next.config.js +++ b/packages/create-content-sdk-app/src/templates/nextjs/next.config.js @@ -1,10 +1,30 @@ const path = require('path'); +const webpack = require('webpack'); const SassAlias = require('sass-alias'); +const fs = require('fs'); /** * @type {import('next').NextConfig} */ +// Determine host role strictly from env for build-time safety +const isEditingHost = !!process.env.SITECORE_EDITING_SECRET; + +// For Rendering Host builds, ensure the editing API folder is removed before Next scans pages +if (!isEditingHost) { + try { + const editingApiDir = path.join(process.cwd(), 'src', 'pages', 'api', 'editing'); + if (fs.existsSync(editingApiDir)) { + fs.rmSync(editingApiDir, { recursive: true, force: true }); + console.log('next.config: removed editing API routes for Rendering Host'); + } + } catch (error) { + console.warn('Failed to remove editing API routes:', error); + } +} + const nextConfig = { + // Default Next.js page extensions + pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], // Allow specifying a distinct distDir when concurrently running app in a container distDir: process.env.NEXTJS_DIST_DIR || '.next', @@ -22,10 +42,6 @@ const nextConfig = { // Disable the X-Powered-By header. Follows security best practices. poweredByHeader: false, - // Expose the editing host flag for build-time dead-code elimination - env: { - NEXT_PUBLIC_EDITING_HOST: process.env.NEXT_PUBLIC_EDITING_HOST || 'false', - }, // use this configuration to ensure that only images from the whitelisted domains // can be served from the Next.js Image Optimization API @@ -65,7 +81,7 @@ const nextConfig = { ]; // Only add FEAAS editing route for Editing Host builds - if (process.env.NEXT_PUBLIC_EDITING_HOST === 'true') { + if (isEditingHost) { routes.push({ source: '/feaas-render', destination: '/api/editing/feaas/render', @@ -80,8 +96,37 @@ const nextConfig = { // Add a loader to strip out getComponentServerProps from components in the client bundle config.module.rules.unshift({ test: /src\\components\\.*\.tsx$/, + // reference this for the editing host exclusion task use: ['@sitecore-content-sdk\\nextjs\\component-props-loader'], }); + + // Exclude editing-only imports (DesignLibrary, .sitecore/import-map) from client bundle when not Editing Host + if (!isEditingHost) { + // Reuse the enhanced component-props loader to strip editing-only code across the app + config.module.rules.unshift({ + test: /src\\.*\.(ts|tsx)$/, + use: ['@sitecore-content-sdk\\nextjs\\component-props-loader'], + }); + + // Client-only aliases and ignore rules for editing-only code + config.resolve = config.resolve || {}; + config.resolve.alias = { + ...config.resolve.alias, + '@sitecore-content-sdk/nextjs/editing': false, + '@sitecore-content-sdk/nextjs/editing/codegen/import-map': false, + // Do NOT alias core editing on server; we strip only client usage + '@sitecore-content-sdk/react/dist/esm/components/DesignLibrary': false, + '.sitecore/import-map': false, + }; + + config.plugins = config.plugins || []; + config.plugins.push( + new webpack.IgnorePlugin({ + resourceRegExp: + /^(?:\.sitecore\/import-map(?:\.ts|\.js)?|@sitecore-content-sdk\/(?:core|nextjs)\/editing(?:\/.*)?|@sitecore-content-sdk\/react\/dist\/esm\/components\/DesignLibrary(?:\.js)?)$/, + }) + ); + } } else { // Force use of CommonJS on the server for FEAAS SDK since Content SDK also uses CommonJS entrypoint to FEAAS SDK. // This prevents issues arising due to FEAAS SDK's dual CommonJS/ES module support on the server (via conditional exports). @@ -95,6 +140,9 @@ const nextConfig = { ...config.externals, ]; } + + // Note: server bundle remains untouched; client-only handling above + <%_ if (helper.isDev) { -%> // monorepo configuration start if (options.isServer) { diff --git a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts index 1e06c069bc..5f2ad6cb6a 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/sitecore.config.ts @@ -4,6 +4,12 @@ import { defineConfig } from '@sitecore-content-sdk/nextjs/config'; * See the documentation for `defineConfig`: * https://doc.sitecore.com/xmc/en/developers/content-sdk/the-sitecore-configuration-file.html */ +// Single source of truth derived from env +const isEditingHost = !!process.env.SITECORE_EDITING_SECRET; + export default defineConfig({ - disableCodeGeneration: process.env.NEXT_PUBLIC_EDITING_HOST !== 'true', + // Persist the flag on config so other parts of the app can read it + isEditingHost, + // Use the flag instead of re-deriving from env + disableCodeGeneration: !isEditingHost, }); diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx b/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx index 25db19008e..f82b7c178e 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/Layout.tsx @@ -7,6 +7,7 @@ import Head from 'next/head'; import { Placeholder, Field, Page, DesignLibrary } from '@sitecore-content-sdk/nextjs'; import Scripts from 'src/Scripts'; import SitecoreStyles from 'src/components/content-sdk/SitecoreStyles'; +import scConfig from 'sitecore.config'; interface LayoutProps { page: Page; } @@ -22,7 +23,8 @@ const Layout = ({ page }: LayoutProps): JSX.Element => { const fields = route?.fields as RouteFields; const mainClassPageEditing = mode.isEditing ? 'editing-mode' : 'prod-mode'; - const isEditingHost = process.env.NEXT_PUBLIC_EDITING_HOST === 'true'; + // Use the public flag exposed by next.config.js (derived from SITECORE_EDITING_SECRET) + const isEditingHost = !!scConfig.isEditingHost; let importMap: any = undefined; if (isEditingHost) { try { @@ -46,7 +48,7 @@ const Layout = ({ page }: LayoutProps): JSX.Element => { {/* root placeholder for the app, which we add components to using route data */}
- {mode.isDesignLibrary && isEditingHost ? ( + {mode.isDesignLibrary ? ( ) : ( <> diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.editing.ts similarity index 78% rename from packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts rename to packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.editing.ts index 32d2fe610d..fedc339f85 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/feaas/render.editing.ts @@ -1,4 +1,4 @@ -import { FEAASRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; +import { FEAASRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; import type { NextApiRequest, NextApiResponse } from 'next'; /** @@ -15,9 +15,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; const baseHandler = new FEAASRenderMiddleware().getHandler(); export default function handler(req: NextApiRequest, res: NextApiResponse) { - if (process.env.NEXT_PUBLIC_EDITING_HOST !== 'true') { - res.status(404).end(); - return; - } return baseHandler(req, res); } diff --git a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.editing.ts similarity index 84% rename from packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts rename to packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.editing.ts index 7165adfc9c..8bf835f452 100644 --- a/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.ts +++ b/packages/create-content-sdk-app/src/templates/nextjs/src/pages/api/editing/render.editing.ts @@ -1,4 +1,4 @@ -import { EditingRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; +import { EditingRenderMiddleware } from '@sitecore-content-sdk/nextjs/editing'; import type { NextApiRequest, NextApiResponse } from 'next'; /** @@ -23,13 +23,8 @@ export const config = { }, }; -// Wire up the EditingRenderMiddleware handler const baseHandler = new EditingRenderMiddleware().getHandler(); export default function handler(req: NextApiRequest, res: NextApiResponse) { - if (process.env.NEXT_PUBLIC_EDITING_HOST !== 'true') { - res.status(404).end(); - return; - } return baseHandler(req as any, res as any); } diff --git a/packages/nextjs/src/config/define-config.ts b/packages/nextjs/src/config/define-config.ts index 30fbbfdee0..cf66238727 100644 --- a/packages/nextjs/src/config/define-config.ts +++ b/packages/nextjs/src/config/define-config.ts @@ -42,6 +42,8 @@ export const getNextFallbackConfig = (config?: SitecoreConfigInput): SitecoreCon process.env.GENERATE_STATIC_PATHS !== undefined ? process.env.GENERATE_STATIC_PATHS.toLowerCase() === 'true' : config?.generateStaticPaths ?? true, + // propagate editing host flag to Next.js layer based on core config/env + isEditingHost: (config as any)?.isEditingHost ?? !!process.env.SITECORE_EDITING_SECRET, }; };