Upgrade JSS 22.x Angular apps to Content SDK 1.0
The Sitecore Content SDK for Angular replaces the JSS SDK for Angular. This topic describes how to migrate an existing JSS Angular app to a Content SDK app using Angular. For simplicity, we'll use the NgModule-based starter that's shipped with JSS SDK for Angular.
The two apps differ in more than package names. The JSS app is built around NgModule, a custom route matcher/resolver pair, a JssContextService that streams state through RxJS, integrated-mode SSR (server.bundle.ts) behind a separate Node proxy, and client-side Cloud SDK initialization.
The Content SDK app is a standalone-first application without NgModule that uses route resolvers, known as loaders, a SitecoreClient, server-side middleware for multisite and personalization, an Express SSR host (server.ts), and a SitecoreContextService exposed as Angular signals.
This topic covers the following:
- What's new in Content SDK for Angular 1.0?
- Create a new Content SDK application
- Configure your environment
- Migrate your components
- Read context data
- Fetch data with SitecoreClient and loaders
- Migrate existing internationalization implementation
- Obsolete and revised functionality
- Optional steps
What's new in Content SDK for Angular 1.0?
Content SDK for Angular consolidates the JSS Angular packages into a single @sitecore-content-sdk/angular package and modernizes the app architecture using the latest Angular features, such as standalone components, signals, and route resolvers:
- Standalone, no
NgModuleusage - boots throughbootstrapApplicationwith anApplicationConfig(app.config.ts) instead ofAppModule/AppServerModule. Components are standalone and declare their own imports. - Route resolvers (known as loaders) - data fetching moves out of
JssContextServiceand into loader functions. These are Angular route resolvers registered withprovideLoaderRegistry()and wired into routes withloaderResolver(). The same loaders run on the server (initial render) and the client (subsequent navigations, via a/_dataendpoint). SitecoreClient- A single client object exposesgetPage,getPreview,getErrorPage,getDictiona ry, andgetHeadLinks. It replaces the JSSGraphQLLayoutService/GraphQLDictionaryServicefactories.- Server middleware - multisite, personalization, bot tracking, and SXA redirects all run as Express middleware on the SSR host (
server.ts) rather than as client-side components. Editing, experimental features, cache admin, revalidation, and the loader-data endpoint are also middleware. The host exports its Node request handler as the module's default export, in addition to the existingreqHandlernamed export. - Signals-based context -
SitecoreContextServiceexposes the currentpage()and helpers likeisEditing()as signals, replacing theRxJSstate observable onJssContextService. sitecore-toolsCLI -sitecore.config.tsandsitecore.cli.config.tsdrive configuration, the generated component map,sites.json, andmetadata.jsonunder.sitecore/.
Create a new Content SDK application
You can scaffold the new app with the latest version of the create-content-sdk-app package:
-
Run the following command:
-
When prompted to choose a template, choose
angular. -
Install dependencies and confirm the app runs before migrating anything:
The generator creates a complete, runnable Content SDK for Angular app with the following:
- Standalone bootstrap (
app.config.ts/app.config.server.ts). - Routing with locale matching, loaders, and 404/500 error routes (
app.routes.ts/app.routes.server.ts). - A
SitecoreClientand out-of-the-box loaders undersrc/content-sdk/. - An Express SSR host (
server.ts) with multisite, personalization, bot tracking, SXA redirects, editing, cache, and loader-data middleware already wired. sitecore.config.ts/sitecore.cli.config.tsand the.sitecore/generated files (component map,sites.json,metadata.json).- A set of out-of-the-box components (
Image,Promo,RichText,Navigation, and so on).
Structure of the generated app
You do not have to author these files, but understanding the mapping from your JSS app makes the migration steps clearer.
-
Bootstrap -
AppModule/AppServerModuleare replaced by anApplicationConfig(app.config.ts), which registers the router, HTTP client, Sitecore providers, the loader registry, the component map, and translation.provideSitecoreAngular()replaces bothJssModule.withComponents(...)and theEDGE_CONFIGprovider in a single call. -
Routing - the custom JSS
UrlMatcher(jssRouteMatcherbuilt onJssRouteBuilderService) is replaced byscLocaleMatcher()that provides locale-aware matching to expose locale as a route param.The JSS resolver (
jssRouteResolver → JssContextService.changeRoute) is replaced byloaderResolver()that runs a named loader and places the result onroute.data:A companion
app.routes.server.tsdeclares the 404/500 server routes so@angular/ssrreturns the correct HTTP status. -
SSR host - the integrated-mode
server.bundle.tsplus the separatenode-xmcloud-proxyproject andproxy.conf.jsare replaced by a single Express host,server.ts, built on@angular/ssr/node. It defines the SDK middleware plugins, including multisite, personalization, bot tracking, SXA redirects, editing (config, render, and experimental features), cache admin, revalidation, and loader-data, and exports the resulting request handler both as a namedreqHandlerexport and as the module's default export. You only need to customize this file if you want to add additional middleware.
Configure your environment
Content SDK centralizes configuration in the sitecore.config.ts file, splits client and server values, and generates environment files. Environment variables change name and shape from the JSS environment.js/JssConfig.
Client-exposed values use the CSDK_PUBLIC_ prefix and are baked into src/environments/environment.ts by npm run gen:env:dev/gen:env:prod. Server-only values are read from process.env, loaded by src/load-env.ts. Create a copy of the .env.example file, rename it to .env, and fill in the appropriate values from SitecoreAI:
Old environment variable | New environment variable |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
| No equivalent variable. Derived by the SDK from Edge/API config |
| No equivalent variable. Derived from |
The following factories you used in JSS to build services from these values have no equivalent.
graphql-client-factorylayout-service-factorydictionary-service-factorysrc/app/lib/config.ts
Migrate your components
In JSS, components were declared in an auto-generated AppComponentsModule through JssModule.withComponents([...]). They used an @Input() rendering, templateUrl / *.html files, and standalone: false. The shared SxaComponent base class read rendering.params.
In Content SDK, components are standalone, declare their own imports, and are discovered by the sitecore-tools CLI into a generated .sitecore/component-map.ts.
The following example shows how new Angular practices can be applied to components:
The key component differences stem from both Angular and Content SDK changes:
-
Base class - new
SxaComponentuses signal inputs and exposesfields(),params(),rendering(),renderingId(), andstyles()as signals/computeds:Components receive
fields/paramsas bound inputs from the placeholder, not a single rendering object. -
Directives -
*scText,*scRichText,*scImage,*scLink, and<sc-placeholder>come from@sitecore-content-sdk/angularand must be added to each component's imports array as they are no longer globally available throughJssModule. -
Variants - JSS selected template variants with
@ViewChild/TemplateRefandrendering.params.FieldNames. Content SDK exports a named class per variant and lets the component map resolve them. For example: -
Host bindings -SXA host classes and IDs move to the host (
{ '[attr.class]': "...", '[attr.id]': 'renderingId()' }) and use signals (styles(),renderingId()). -
Editing checks - replaces
JssContextService.state.subscribe(... context.pageEditing ...)withinject(SitecoreContextService).isEditing(). See Read context data from SitecoreContextService for more information.
The auto-generated AppComponentsModule is replaced by .sitecore/component-map.ts, which sitecore-tools generates from the components under src/app/components. After adding or moving a component, regenerate the map with the following command:
The map is provided through SITECORE_COMPONENT_MAP in app.config.ts and passed to the editing config middleware in server.ts, both of which are already wired in the generated app.
You can disable automatic component-map generation and maintain one manually. For more information, see Register a component in the component map.
Read context data
If your JSS components or services read from Sitecore context, you must migrate those reads. JSS exposed context as an RxJS observable (JssContextService.state) built on JssStateService<JssState>, plus a custom JssState class. Components subscribed to it and had to unsubscribe in ngOnDestroy.
Content SDK exposes context as signals through SitecoreContextService. There is no JssState, no changeRoute, and no manual subscription management. Derive view state with computed() and side effects with effect() instead of subscribing.
JSS (Old) | Content SDK (New) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
| Handled by loaders and |
| Handled by the |
For example, the JSS image component subscribed to context to compute an editing flag:
The Content SDK equivalent reads the signal directly inside a computed(), with no subscription to clean up:
Language switching likewise moves from an RxJS subscription in AppComponent to an effect() in the root component that reads context.page()?.locale.
Fetch data with SitecoreClient and loaders
SitecoreClient and loadersIf you fetched layout, dictionary, or other Sitecore data directly (through JssLayoutService, JssContextService.changeRoute, or the GraphQL service factories), migrate that logic to SitecoreClient.
For one-off, non-route data fetches, you can call the getClient() method on getPage, getDictionary, getErrorPage, or getHeadLinks) directly:
Migrate existing internationalization implementation
JSS wired @ngx-translate to a custom JssTranslationLoaderService/JssTranslationClientLoaderService pair that called a GraphQLDictionaryService (created by dictionary-service-factory) and used TransferState to pass server data to the client. AppComponent switched languages by subscribing to JssContextService.state.
The new app provides translation out of the box through SitecoreTranslateLoader (registered in app.config.ts) and a dictionary route loader that calls getClient().getDictionary(...), keyed by the resolved site and locale. Language switching uses the signal-based effect described in Read context data.
Delete src/app/i18n/* and dictionary-service-factory.ts from your old code. Do not port them. If you used dictionary phrases in components, the lookup API (@ngx-translate translate pipe/service) is unchanged.
Obsolete and revised functionality
Some parts of existing JSS apps are either revised or no longer required in Content SDK apps, meaning they can be removed. However, if your app contains custom code that uses any of this obsolete functionality, or if you have any customizations in the affected files or folders, you will need to modify your app accordingly.
JSS (Old) | Content SDK (New) |
|---|---|
|
|
|
|
|
|
|
|
| Dictionary loader with |
| Configured internally by |
|
|
|
|
| .sitecore/component-map.ts + SITECORE_COMPONENT_MAP |
| provideSitecoreAngular({ sitecoreConfig, sitecoreClient }) |
| Signal |
|
|
|
|
|
|
|
|
|
|
|
|
No in-app multisite |
|
No loader cache |
|
Removal of Experience Editor
The Content SDK does not support the Experience Editor. During migration, drop anything in your JSS app that exists solely to support it.
The following table shows how JSS packages map to Content SDK packages:
JSS (Old) | Content SDK (New) |
|---|---|
|
|
| Folded into |
| Server analytics via |
|
|
Optional steps
The following steps apply only if you customized personalization, multisite, or other middleware in the proxy companion application in JSS. Otherwise, you can skip this section.
Migrate personalization
In the JSS app, personalization was driven from the client: CloudSdkInitComponent called CloudSDK({...}).addEvents().initialize() in the browser, and CdpPageViewComponent subscribed to context state and called pageView(...) from @sitecore-cloudsdk/events/browser. Variant resolution depended on the separate Node proxy.
The new app handles personalization out of the box in two places:
-
Variant resolution and server middleware -
createPersonalizeMiddleware(...)inserver.tsidentifies page/component variants through Sitecore CDP and writes them ontoreq.scParams. The page loader reads them withgetVariantId(context)/getComponentVariantIds(context)and passes them togetClient().getPage(..., { personalize: { ... } }). This is already wired; configure it throughconfig.personalizerather than in components. -
Page-view analytics and
SITECORE_ANALYTICSfacade - The generatedCdpPageViewComponentis template-less, readsSitecoreContextService.page()in aneffect(), and dispatches through the injectedSITECORE_ANALYTICSfacade. The Cloud SDK is no longer initialized by hand in the browser:Remove
CloudSdkInitComponent,ScriptsModule/ScriptsComponent, and the@sitecore-cloudsdk/*browser imports from your old code. Do not port them. If you had custom event logic (extra events, consent handling), reimplement it againstSITECORE_ANALYTICS.
Personalize requires Edge configuration (context ID/client context ID) and does not run against local containers. The middleware disables itself when that configuration is missing, which is expected in local development.
Migrate multisite and other middleware customizations
The JSS Angular starter was effectively single-site: sitecoreSiteName came from the environment, and multisite resolution was handled outside the Angular app by the node-xmcloud-proxy companion app. There was no in-app site resolver.
The new app provides multisite support out-of-the-box with createMultisiteMiddleware(...) plus a generated .sitecore/sites.json file produced by the sitecore-tools build command. It resolves the site per request (sc_site query → cookie → hostname → default), writes it onto req.scParams, and loaders read it with getSiteName(context). This works out of the box. If you previously had no multisite logic, keep this as is.
The new app also wires up the following middleware out of the box, so you do not need to reimplement them unless you had custom logic beyond what they provide:
createBotTrackingMiddleware(...)- detects bots by User-Agent, sets thesc_botcookie, and sends a dedicated bot page-view event. It does not run in dev/localhost environments.createRedirectsMiddleware(...)- matches each request against the site's Sitecore redirects (locale, static, and regex rules) and issues a 301/302 redirect or an internal server-transfer rewrite. If your old JSS setup had custom redirect logic in thenode-xmcloud-proxycompanion app or elsewhere, review whether your Sitecore redirect items already cover it before writing custom code.createExperimentalFeaturesMiddleware()- exposes an/api/editing/experimentalendpoint that reports which Content SDK experimental features are available and enabled.
If you had other custom middleware in your old setup (custom headers, host rules, or extra proxy behavior not covered by the middleware above), reimplement it in server.ts. The host exposes a shared middlewareMatcher that controls which requests the request-scoped middlewares process. The SDK already skips /api/*, /sitecore/*, static files, and editing/preview requests:
The order of the middleware matters:
createExperimentalFeaturesMiddlewareruns betweencreateEditingConfigMiddlewareandcreateEditingRenderMiddleware.createMultisiteMiddlewaremust run beforecreateBotTrackingMiddleware,createRedirectsMiddleware, andcreatePersonalizeMiddleware, all of which depend on the resolved site.createBotTrackingMiddlewaremust run beforecreatePersonalizeMiddlewareso the bot cookie is set before personalize decides whether to skip the request.createRedirectsMiddlewaremust run beforecreatePersonalizeMiddlewareso a redirect can short-circuit the request before a CDP call is made.
Add your custom middleware around these, not in place of them.
Next steps
To finalize the upgrade process, make sure you resolve any errors and warnings you encounter. Use the following items as a checklist to verify all the functionality:
- Bring your components across one at a time. Convert each component to standalone with signal inputs, add the required
*sc*directives to imports, and runnpm run sitecore-tools:generate-mapso it appears in the.sitecore/component-map.tsfile. - Run
npm run devand verify routing, locale prefixes, dictionary phrases, and editing in Sitecore Pages (metadata mode). - Configure your sites and verify multisite resolution against
.sitecore/sites.json. - Configure Edge (context ID/client context ID) and verify personalization and CDP page-view events in a deployed environment as these do not run against local containers.
- Review the Loaders cache layer and tags revalidation topic. Consider using hooks and the POST
/api/revalidateendpoint for your hosting environment.