Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4e866f8
Route Edge requests and returned URLs through SITECORE_EDGE_HOSTNAME
MenKNas Jan 30, 2026
a580791
Fix failing test
MenKNas Feb 2, 2026
a72243c
Fix error pages null handling + document custom Edge hostname env vars
MenKNas Feb 2, 2026
52709e2
Fix failing api verification issue
MenKNas Feb 2, 2026
90871de
Minor comment adjustments
MenKNas Feb 2, 2026
6fb96ae
Adjust CHANGELOG.md
MenKNas Feb 2, 2026
6224c9c
Fix regex analysis issue
MenKNas Feb 2, 2026
45d9344
Fix verify api extractor issue
MenKNas Feb 2, 2026
64062b0
Fix api verification issue
MenKNas Feb 3, 2026
49ba70e
Add opt-in layout URL rewrite for media content
MenKNas Feb 10, 2026
6f04630
Merge branch 'dev' into feature/JSS-7383-add-custom-hostname-support
MenKNas Feb 10, 2026
c1b72b4
Potential fix for code scanning alert no. 50: Incomplete regular expr…
MenKNas Feb 10, 2026
c8b9073
Potential fix for code scanning alert no. 49: Incomplete regular expr…
MenKNas Feb 10, 2026
25f5c7c
Fix api extractor issue
MenKNas Feb 10, 2026
eaeb11c
Address PR comments 1
MenKNas Feb 13, 2026
7ed5bb4
Fix test related issue
MenKNas Feb 13, 2026
1191572
Fix api-extractor issues and html related false warning
MenKNas Feb 13, 2026
7b92bc5
Address PR comments 2
MenKNas Feb 16, 2026
cadf3fc
Fix API extractor issue
MenKNas Feb 16, 2026
f34cd5f
Address PR comments 3
MenKNas Feb 17, 2026
243bcf9
Update packages/core/src/constants.ts
MenKNas Feb 17, 2026
f7a82fc
Adjustments
MenKNas Feb 17, 2026
8fce771
Adjustments
MenKNas Feb 17, 2026
f22711f
Address PR comments 4
MenKNas Feb 17, 2026
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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Our versioning strategy is as follows:
### 🎉 New Features & Improvements

* `[nextjs]` `[create-content-sdk-app]` Enable Next.js 16 Cache Components and Turbopack File System Caching ([#334](https://github.com/Sitecore/content-sdk/pull/334))
- Enabled `cacheComponents: true` for explicit caching with "use cache" directive
- Enabled `experimental.turbopackFileSystemCacheForDev: true` for faster dev startup (beta)
- Available in both Pages Router and App Router templates

* `[core]` `[content]` `[nextjs]` Support custom Edge hostnames via `SITECORE_EDGE_PLATFORM_HOSTNAME` (Next.js: `NEXT_PUBLIC_SITECORE_EDGE_PLATFORM_HOSTNAME`) ([#359](https://github.com/Sitecore/content-sdk/pull/359))
- New `rewriteMediaUrls` option: when `true`, rewrites layout media URLs to the custom Edge hostname; when a function, applies a custom string transformer.

* Search integration ([#295](https://github.com/Sitecore/content-sdk/pull/295))
* `[search]` New `@sitecore-content-sdk/search` package providing search functionality
Expand Down
3 changes: 3 additions & 0 deletions api-extractor.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
"tsdoc-html-tag-missing-greater-than": {
"logLevel": "none"
},
"tsdoc-html-tag-missing-equals": {
"logLevel": "none"
},
"tsdoc-at-sign-in-word": {
"logLevel": "none"
}
Expand Down
21 changes: 18 additions & 3 deletions packages/content/api/content-sdk-content.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export const addServerComponentPreviewHandler: (callback: (eventArgs: ComponentP
// @internal
export function addStyleElement(stylesContent: string): void;

// @internal
export function applyMediaUrlRewrite<T>(value: T, transform: (s: string) => string): T;

// @public
export class CdpHelper {
static getComponentFriendlyId(pageId: string, componentId: string, language: string, scope?: string): string;
Expand Down Expand Up @@ -183,6 +186,9 @@ export interface ComponentUpdateEventArgs {
name: string;
}

// @public
export function containsDefaultEdgeHost(str: string): boolean;

// @internal
export const createComponentInstance: (importMap: ImportEntry[], previewEventArgs: ComponentPreviewEventArgs) => unknown;

Expand Down Expand Up @@ -416,11 +422,11 @@ export enum ErrorPage {
export type ErrorPages = {
notFoundPage: {
rendered: LayoutServiceData;
};
} | null;
notFoundPagePath: string;
serverErrorPage: {
rendered: LayoutServiceData;
};
} | null;
serverErrorPagePath: string;
};

Expand Down Expand Up @@ -532,6 +538,9 @@ export const getContentSdkPagesClientData: () => Record<string, Record<string, u
// @public
export const getContentStylesheetLink: (layoutData: LayoutServiceData, sitecoreEdgeContextId: string, sitecoreEdgeUrl?: string) => HTMLLink | null;

// @internal
export function getDefaultMediaUrlTransformer(edgeUrl: string): (value: string) => string;

// Warning: (ae-forgotten-export) The symbol "DesignLibraryComponentPreviewErrorEvent" needs to be exported by the entry point api-surface.d.ts
//
// @internal
Expand Down Expand Up @@ -977,6 +986,9 @@ export const resetEditorChromes: () => void;

export { RetryStrategy }

// @public
export function rewriteEdgeHostInResponse<T>(response: T, edgeUrl: string): T;

// @public
export type RobotsQueryResult = {
site: {
Expand Down Expand Up @@ -1087,6 +1099,8 @@ export type SitecoreCliConfigInput = {
// @public
export class SitecoreClient implements BaseSitecoreClient {
constructor(initOptions: SitecoreClientInit);
// @internal
protected applyContentRewrite(layout: LayoutServiceData): LayoutServiceData;
// (undocumented)
protected clientFactory: GraphQLRequestClientFactory;
// (undocumented)
Expand Down Expand Up @@ -1190,6 +1204,7 @@ export type SitecoreConfigInput = {
enabled?: boolean;
locales?: string[];
};
rewriteMediaUrls?: boolean | ((value: string) => string);
disableCodeGeneration?: boolean;
};

Expand Down Expand Up @@ -1338,7 +1353,7 @@ export type WriteImportMapArgsInternal = WriteImportMapArgs & {

// Warnings were encountered during analysis:
//
// src/client/sitecore-client.ts:58:3 - (ae-forgotten-export) The symbol "PageModeName" needs to be exported by the entry point api-surface.d.ts
// src/client/sitecore-client.ts:63:3 - (ae-forgotten-export) The symbol "PageModeName" needs to be exported by the entry point api-surface.d.ts
// src/editing/codegen/preview.ts:108:5 - (ae-forgotten-export) The symbol "ComponentImport_2" needs to be exported by the entry point api-surface.d.ts
// src/tools/generate-map.ts:24:3 - (ae-incompatible-release-tags) The symbol "mapTemplate" is marked as @public, but its signature references "ComponentMapTemplate" which is marked as @internal
// src/tools/generate-map.ts:24:3 - (ae-incompatible-release-tags) The symbol "mapTemplate" is marked as @public, but its signature references "EnhancedComponentMapTemplate" which is marked as @internal
Expand Down
6 changes: 3 additions & 3 deletions packages/content/src/client/edge-proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { expect } from 'chai';
import { constants } from '@sitecore-content-sdk/core';
import { getEdgeProxyContentUrl, getEdgeProxyFormsUrl } from './edge-proxy';

const { SITECORE_EDGE_URL_DEFAULT } = constants;
const { SITECORE_EDGE_PLATFORM_URL_DEFAULT } = constants;

describe('edge-proxy', () => {
describe('getEdgeProxyContentUrl', () => {
it('should return url', () => {
const url = getEdgeProxyContentUrl();

expect(url).to.equal(`${SITECORE_EDGE_URL_DEFAULT}/v1/content/api/graphql/v1`);
expect(url).to.equal(`${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/content/api/graphql/v1`);
});

it('should return url when custom sitecoreEdgeUrl is provided', () => {
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('edge-proxy', () => {
const url = getEdgeProxyFormsUrl(sitecoreEdgeContextId, formId);

expect(url).to.equal(
`${SITECORE_EDGE_URL_DEFAULT}/v1/forms/publisher/${formId}?sitecoreContextId=${sitecoreEdgeContextId}`
`${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/forms/publisher/${formId}?sitecoreContextId=${sitecoreEdgeContextId}`
);
});

Expand Down
24 changes: 15 additions & 9 deletions packages/content/src/client/edge-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { constants } from '@sitecore-content-sdk/core';
import { normalizeUrl } from '@sitecore-content-sdk/core/tools';

const { SITECORE_EDGE_URL_DEFAULT } = constants;
/**
* Resolves the base Edge URL from config. Caller should pass the resolved Edge URL from config.
* @param {string} [sitecoreEdgeUrl] - The base Edge URL from config. Defaults to platform URL.
* @internal
*/
const getBaseEdgeUrl = (
sitecoreEdgeUrl: string = constants.SITECORE_EDGE_PLATFORM_URL_DEFAULT
): string => normalizeUrl(sitecoreEdgeUrl);

/**
* Generates a URL for accessing Sitecore Edge Platform Content using the provided endpoint and context ID.
* @param {string} [sitecoreEdgeUrl] - The base endpoint URL for the Edge Platform. Default is https://edge-platform.sitecorecloud.io
* @param {string} [sitecoreEdgeUrl] - The base endpoint URL for the Edge Platform (resolved at config level). Defaults to platform URL.
* @returns {string} The complete URL for accessing content through the Edge Platform.
* @public
*/
export const getEdgeProxyContentUrl = (sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) =>
`${normalizeUrl(sitecoreEdgeUrl)}/v1/content/api/graphql/v1`;
export const getEdgeProxyContentUrl = (
sitecoreEdgeUrl: string = constants.SITECORE_EDGE_PLATFORM_URL_DEFAULT
) => `${getBaseEdgeUrl(sitecoreEdgeUrl)}/v1/content/api/graphql/v1`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let me clarify again the difference between Experience Edge and Edge Platform URLs, as there appears to be a misunderstanding.

Experience Edge (https://edge.sitecorecloud.io)
– Used for media URLs.

Edge Platform (https://edge-platform.sitecorecloud.io)
– Used by our services to resolve endpoints.

Based on the changes you introduced, Experience Edge is now being referenced when resolving service endpoints. This is not correct. We should continue using the default Edge Platform URL for service-related requests.

The Experience Edge URL should be referenced only when replacing media URLs.
This is the only place where the Experience Edge URL environment variable should be used, in order to allow overriding it when necessary.


/**
* Generates a URL for accessing Sitecore Edge Platform Forms using the provided form ID and context ID.
* @param {string} sitecoreEdgeContextId - The unique context id.
* @param {string} formId - The unique form id.
* @param {string} [sitecoreEdgeUrl] - The base endpoint URL for the Edge Platform. Default is https://edge-platform.sitecorecloud.io
* @param {string} [sitecoreEdgeUrl] - The base endpoint URL for the Edge Platform (resolved at config level). Defaults to platform URL.
* @returns {string} The complete URL for accessing forms through the Edge Platform.
* @internal
*/
export const getEdgeProxyFormsUrl = (
sitecoreEdgeContextId: string,
formId: string,
sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT
sitecoreEdgeUrl: string = constants.SITECORE_EDGE_PLATFORM_URL_DEFAULT
) =>
`${normalizeUrl(
sitecoreEdgeUrl
)}/v1/forms/publisher/${formId}?sitecoreContextId=${sitecoreEdgeContextId}`;
`${getBaseEdgeUrl(sitecoreEdgeUrl)}/v1/forms/publisher/${formId}?sitecoreContextId=${sitecoreEdgeContextId}`;
107 changes: 99 additions & 8 deletions packages/content/src/client/sitecore-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable dot-notation */
/* eslint-disable dot-notation */
/* eslint-disable no-unused-expressions, @typescript-eslint/no-unused-expressions */
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';
import { DocumentNode } from 'graphql';
import { DefaultRetryStrategy, NativeDataFetcher } from '@sitecore-content-sdk/core';
import { DefaultRetryStrategy, NativeDataFetcher, constants } from '@sitecore-content-sdk/core';

const { SITECORE_EDGE_PLATFORM_URL_DEFAULT } = constants;
import { ErrorPage, SitecoreClient } from './sitecore-client';
import { LayoutKind, DesignLibraryMode } from '../../src/editing';
import { LayoutServiceData } from '../../layout';
Expand Down Expand Up @@ -484,6 +486,67 @@ describe('SitecoreClient', () => {
});
});

it('should apply content rewrite when rewriteMediaUrls is a function', async () => {
const path = '/test/path';
const locale = 'en-US';
const siteInfo = { name: 'default-site', hostName: 'example.com', language: 'en' };
const rawLayout = {
sitecore: {
route: { name: 'home', placeholders: {} },
context: { site: siteInfo, pageState: LayoutServicePageState.Normal },
},
};
layoutServiceStub.fetchLayoutData.returns(rawLayout);
const stringTransformer = (value: string) =>
value === 'home' ? 'rewritten' : value;
const clientWithRewrite = new SitecoreClient({
...defaultInitOptions,
rewriteMediaUrls: stringTransformer,
} as any);
(clientWithRewrite as any).layoutService = layoutServiceStub;

const result = await clientWithRewrite.getPage(path, { locale });

expect(result?.layout.sitecore.route?.name).to.equal('rewritten');
});

it('should apply default Edge host rewrite when rewriteMediaUrls is true and custom hostname is set', async () => {
const path = '/test/path';
const locale = 'en-US';
const siteInfo = { name: 'default-site', hostName: 'example.com', language: 'en' };
const rawLayout = {
sitecore: {
route: {
name: 'home',
placeholders: {},
fields: {
image: { value: { src: 'https://edge.sitecorecloud.io/-/media/hero.jpg' } },
},
},
context: { site: siteInfo, pageState: LayoutServicePageState.Normal },
},
};
layoutServiceStub.fetchLayoutData.returns(rawLayout);
const clientWithRewrite = new SitecoreClient({
...defaultInitOptions,
api: {
...defaultInitOptions.api,
edge: {
...defaultInitOptions.api.edge,
edgeUrl: 'https://custom.example.com',
},
},
rewriteMediaUrls: true,
} as any);
(clientWithRewrite as any).layoutService = layoutServiceStub;

const result = await clientWithRewrite.getPage(path, { locale });

expect(
(result?.layout.sitecore.route?.fields?.image?.value as { src: string }).src
).to.equal('https://custom.example.com/-/media/hero.jpg');
});

it('should pass fetchOptions to layoutService when calling getPage', async () => {
const path = '/test/path';
const locale = 'en-US';
Expand Down Expand Up @@ -1350,11 +1413,11 @@ describe('SitecoreClient', () => {

expect(result).to.deep.equal([
{
href: 'https://edge.example.com/v1/files/pages/styles/content-styles.css?sitecoreContextId=test-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/pages/styles/content-styles.css?sitecoreContextId=test-context-id`,
rel: 'stylesheet',
},
{
href: 'https://edge.example.com/v1/files/components/styles/foo.css?sitecoreContextId=test-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/components/styles/foo.css?sitecoreContextId=test-context-id`,
rel: 'stylesheet',
},
]);
Expand All @@ -1376,11 +1439,11 @@ describe('SitecoreClient', () => {

expect(result).to.deep.equal([
{
href: 'https://edge.example.com/v1/files/pages/styles/content-styles.css?sitecoreContextId=client-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/pages/styles/content-styles.css?sitecoreContextId=client-context-id`,
rel: 'stylesheet',
},
{
href: 'https://edge.example.com/v1/files/components/styles/foo.css?sitecoreContextId=client-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/components/styles/foo.css?sitecoreContextId=client-context-id`,
rel: 'stylesheet',
},
]);
Expand All @@ -1393,7 +1456,7 @@ describe('SitecoreClient', () => {
});
expect(result).to.deep.equal([
{
href: 'https://edge.example.com/v1/files/components/styles/foo.css?sitecoreContextId=test-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/components/styles/foo.css?sitecoreContextId=test-context-id`,
rel: 'stylesheet',
},
]);
Expand All @@ -1406,7 +1469,7 @@ describe('SitecoreClient', () => {
});
expect(result).to.deep.equal([
{
href: 'https://edge.example.com/v1/files/pages/styles/content-styles.css?sitecoreContextId=test-context-id',
href: `${SITECORE_EDGE_PLATFORM_URL_DEFAULT}/v1/files/pages/styles/content-styles.css?sitecoreContextId=test-context-id`,
rel: 'stylesheet',
},
]);
Expand Down Expand Up @@ -1454,6 +1517,34 @@ describe('SitecoreClient', () => {
expect(result).to.equal(xmlContent);
});

it('should rewrite Edge hostnames in sitemap path and XML when custom hostname is configured', async () => {
const clientWithCustomEdge = new SitecoreClient({
...defaultInitOptions,
api: {
...defaultInitOptions.api,
edge: {
...defaultInitOptions.api.edge,
edgeUrl: 'https://custom.example.com',
},
},
} as any);

const edgeSitemapPath = 'https://edge.sitecorecloud.io/sitemap.xml';
const xmlContent =
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://edge.sitecorecloud.io/a</loc></url></urlset>';

sitemapXmlServiceStub.getSitemap.resolves(edgeSitemapPath);
const dataFetcherStub = sandbox
.stub(NativeDataFetcher.prototype, 'fetch')
.resolves({ data: xmlContent, status: 200, statusText: 'OK' });

const result = await clientWithCustomEdge.getSiteMap({ ...defaultReqConfig });

expect(getGraphqlSitemapXMLServiceStub.calledWith(defaultReqConfig.siteName)).to.be.true;
expect(dataFetcherStub.calledWith('https://custom.example.com/sitemap.xml')).to.be.true;
expect(result).to.include('https://custom.example.com/a');
});

it('should fetch specific sitemap when ID is provided', async () => {
const sitemapId = '1';
const absoluteSitemapPath = 'https://cdn.example.com/sitemap-1.xml';
Expand Down
Loading
Loading