Skip to main content
advancedUpdated July 13, 2026·13 min read

Composable Storefront on CCv2: Build, Runtime Configuration, and OCC Connectivity

Deploy Composable Storefront on CCv2 with reproducible artifacts, runtime OCC configuration, CORS, private API access, and browser/SSR verification.

CCI

Commerce Cloud Integrations

SAP Commerce engineering team

Architecture, integrations, performance, upgrades, and Composable Storefront on SAP Commerce Cloud.

composablespartacusccv2occdeploymentcorsssr

Composable Storefront on SAP Commerce Cloud is two deployable concerns joined by an API contract: the Commerce application exposes OCC, while the JavaScript storefront application is built and served by the JavaScript storefront services, optionally with Node SSR. Most failed deployments are not Angular defects. They are mismatches between the artifact that CCv2 discovers, the runtime endpoint it injects, the proxy/CORS policy that receives the call, and the configuration that the browser and SSR process actually use.

This guide is scoped to Commerce Cloud 2211-style JavaScript storefront deployment. Cloud Portal schema, supported Node lines, and library package paths change over time; treat examples as a verification model, not a substitute for the exact environment documentation. It intentionally does not repeat general CCv2 web/network architecture or basic OCC contract ownership.

Model the deployment as four contracts#

When a deployment fails, identify which contract is broken before changing anything.

ContractOwnsFailure signatureEvidence that proves it
Source/buildRepository layout, manifest application entry, lock file, scripts, Node compatibilityBuild fails, or the wrong/stale artifact is deployedBuild log plus artifact tree under the configured application path.
Runtime endpointindex.html placeholder, API endpoint selection, per-app service propertiesBrowser calls old/wrong host, SSR works differently from CSR, blue/green driftDelivered index.html, browser Network URL, JS App and JsApp-SSR service config.
Transport/securityDNS, TLS, proxy, CORS, IP/API filtering, headers, frame policyPreflight failure, 400/403, browser-only failure, SmartEdit iframe blockedRaw OPTIONS/GET responses and proxy/API policy evidence.
Storefront behaviorprovideConfig, SSR server configuration, base site/context valuesCorrect endpoint reaches OCC but wrong site/language/data is renderedEffective config plus representative page/OCC trace.

The contracts are deliberately separate. A browser CORS message does not prove CORS is the cause; a 400 response can be a proxy/header-size issue. A valid browser base URL does not prove Node SSR uses the same endpoint. A successful build does not prove CCv2 built the source you expected.

Make the JavaScript application discoverable and reproducible#

The JavaScript storefront manifest declares applications under js-storefront/. SAP's Add Applications to JavaScript Storefronts document is the source of truth for the current manifest schema. This is the relevant shape:

json
{
  "applications": [
    {
      "name": "storefront",
      "path": "storefront",
      "enabledRepositories": ["spartacus-6-cdn"]
    }
  ]
}

Do not infer the repository name from the storefront version. SAP's 2211 documentation specifies the repository enablement separately from the particular 5.x, 6.x, or 2211.x package version installed by the project. Keep the exact registry/RBSC configuration and lock-file policy in source control; uncontrolled lock-file regeneration is a dependency upgrade disguised as a deployment fix.

The name/path relationship is operationally significant:

text
repository root/
└── js-storefront/
    ├── manifest.json
    └── storefront/                     # application.path
        ├── package.json
        ├── yarn.lock or package-lock.json
        ├── src/
        └── dist/
            └── storefront/             # dist/<application.name>
                ├── index.html
                ├── browser/ ...
                └── server/ ...          # when SSR build emits it

For a source application, the platform expects the application package.json, lock file, and source under application.path. For a prebuilt application, the output—including index.html—must be under application.path/dist/application.name.

There is a sharp edge: for each manifest application, CCv2 checks whether application.path/dist already exists. If it does not, it installs dependencies and runs yarn build for CSR or yarn build:ssr for SSR. If it does exist, the platform assumes it contains a prebuilt artifact and skips that build step. A stale checked-in dist/ directory can therefore deploy an old bundle while the commit looks correct.

bash
# Run in CI or locally before creating a build; do not delete artifacts blindly in a shared branch.
test -f js-storefront/storefront/package.json
test -f js-storefront/storefront/yarn.lock || test -f js-storefront/storefront/package-lock.json
test -f js-storefront/storefront/src/index.html

# If this project intentionally commits a prebuilt artifact, validate its exact discovery path.
test -f js-storefront/storefront/dist/storefront/index.html

Decide explicitly which of these two models the repository uses:

ModelRepository containsCCv2 behaviorRequired release control
Source buildSource, manifest, package manifest, reproducible lock file; no accidental distInstalls dependencies and invokes the declared build scriptBuild locally/CI with the same supported Node line and preserve lock-file integrity.
Prebuilt artifactdist/<application.name> with the complete browser/SSR outputTreats dist as authoritative and skips the buildGenerate artifact in controlled CI, publish exactly one build output, verify its commit/version marker.

Never mix them accidentally. A failing yarn build:ssr should not be “fixed” by committing whatever local dist happened to exist.

Diagnose build failures by their actual contract#

Build log symptomCheck firstCorrective direction
package.json / lock file not foundapplication.path, case sensitivity, manifest pathFix the manifest/repository layout; do not alter Angular code.
yarn build works but SSR deploy failsbuild:ssr script, server output, Node runtime compatibilityRun the actual SSR script locally using the target support matrix.
Missing Composable package or unauthorized registry.npmrc/.yarnrc, RBSC configuration, entitlementRestore the approved SAP registry configuration; regenerate lock files only as a controlled dependency change.
Build skipped unexpectedlyExisting application.path/distRemove/replace stale prebuilt output through the project’s intended artifact workflow.
Node engine/module syntax errorengines, Angular/CLI/build tool compatibility, Cloud Portal supported Node versionsSelect a Node line supported by both the deployed Commerce Cloud environment and the installed Angular/Composable release.
manifest.json / server main / SSR package cannot be resolvedBuilt artifact tree and the server.ts entry expected by the versionRepair the application build contract; do not solve it through runtime service properties.

SAP has withdrawn older Node lines over time; KBA 3363029 is a useful reminder that a locally supported Node release can be removed from the platform. Do not pin an example Node major from an old blog. Verify the current Cloud Portal options and the package manager engines constraints for the exact storefront release.

Configure OCC at runtime, once and deliberately#

The runtime base URL is not a cosmetic Angular setting. It determines where every browser and server-side OCC request goes and whether blue/green routing can work correctly.

For a JavaScript storefront application, use SAP's documented placeholder in the application index.html:

html
<!-- src/index.html or the generated prebuilt index.html -->
<meta name="occ-backend-base-url" content="OCC_BACKEND_BASE_URL_VALUE" />

At startup, the platform replaces this placeholder with the first API endpoint it created for the application. SAP's base-URL configuration documentation explains the precedence rule that catches many teams: backend.occ.baseUrl supplied through provideConfig() takes precedence over this meta tag. If you expect runtime injection, remove the competing hard-coded baseUrl; otherwise the document can contain the correct platform value while the application continues calling the old host.

typescript
// Do not keep this hard-coded alongside the runtime meta tag.
provideConfig({
  backend: {
    occ: {
      baseUrl: 'https://old-api.example.invalid',
    },
  },
});

The placeholder is intentionally ignored when it is empty or still contains OCC_BACKEND_BASE_URL_VALUE outside the platform replacement flow. Validate the served HTML, not only source control:

bash
curl --silent --show-error --fail \
  'https://storefront.example.test/' \
  | grep -oE '<meta name="occ-backend-base-url" content="[^"]+"'

Decide endpoint selection per application#

There are two supported deployment shapes, and they serve different goals.

NeedConfigurationTrade-off
Standard platform routing / blue-green-friendly endpoint selectionKeep OCC_BACKEND_BASE_URL_VALUE in index.html; let the platform inject the appropriate API endpointBrowser and app URL must be valid in each target environment.
Explicit backend per named JS AppSet ccv2.occ.backend.base.url.<jsapp>.value in JavaScript storefront service configurationYou own endpoint drift and must configure it in both JS App and JsApp-SSR when SSR is enabled.
Canary deploymentKeep the documented OCC_BACKEND_BASE_URL_VALUE placeholderSAP warns not to hard-code the endpoint or use the explicit per-app property for the canary routing case.

For example, an explicitly pinned application named storefront uses this service property name:

text
ccv2.occ.backend.base.url.storefront.value=https://api.example.test

If SSR is enabled, set that same per-app property in the JavaScript App service and the JsApp-SSR service configuration. A browser-only setting produces an easy-to-miss split: CSR goes to the intended API while Node SSR may use a different/default endpoint or fail authorization. SAP documents the property and dual-service requirement in Add Applications to JavaScript Storefronts.

Treat service-property values as environment configuration, not as source code. Do not put passwords, OAuth client secrets, API keys, or user-specific values into index.html, runtime metadata, or a client-side config service. Anything delivered to the browser is public.

Prove that browser and SSR call the same intended backend#

Create a small endpoint matrix for each deployment. The exact header names and gateway behavior are environment-specific, so compare URLs/statuses and approved trace IDs rather than assuming one technology produces the same headers everywhere.

CallerHow to proveExpected result
Browser CSRDevTools Network on initial hydration and later navigationOCC requests use the injected/pinned API host and correct base site.
SSR NodeInitial HTML request plus Node/OCC trace correlationServer-initiated OCC requests reach the same intended API policy/region.
Direct public API testAuthorized curl to a public OCC resourceReturns expected CORS/security response without credentials being exposed.
SmartEdit previewOpen preview route inside SmartEditCorrect page data and no iframe/frame-policy violation.
Blue/green or canaryDeployment preview/canary endpoint testThe document placeholder resolves to the deployment-appropriate API, not a hard-coded old environment.
bash
# CORS-independent API reachability probe. Use a public, non-mutating resource.
curl --silent --show-error --fail --include \
  'https://api.example.test/occ/v2/electronics-spa/languages'

# Browser preflight probe: origin must be an allowed storefront origin.
curl --silent --show-error --include --request OPTIONS \
  -H 'Origin: https://storefront.example.test' \
  -H 'Access-Control-Request-Method: GET' \
  -H 'Access-Control-Request-Headers: authorization,content-type' \
  'https://api.example.test/occ/v2/electronics-spa/languages'

Keep access-token and cookie headers out of screenshots, logs, issue attachments, and shell history. A 401/403 from this anonymous probe may be correct for a protected resource; select a public endpoint for the test.

Configure CORS as an exact origin policy#

Composable Storefront can be on a different origin from OCC. Browsers enforce CORS; Node SSR does not enforce browser CORS, which is why a server-rendered page can appear healthy while browser interactions fail.

Browser observationLikely causeEvidence to collectSafe change
Preflight has no Access-Control-Allow-OriginOrigin missing from Commerce CORS configurationRaw OPTIONS request with OriginAdd the precise storefront origin.
Credentials request with wildcard originAccess-Control-Allow-Credentials: true cannot be combined with * originBrowser request mode and response headersUse explicit allowed origin(s), never a wildcard.
CORS error plus HTTP 400Often a rejected/oversized request header, not CORSProxy/API log and response statusDiagnose header size/cookie/token bloat before changing CORS.
CSR fails but SSR initially worksBrowser is blocked by CORS while Node reaches OCCSide-by-side SSR and browser request tracesCorrect origin/method/header policy.
Only one site/language failsBase URL/site context or CDN origin variationEffective URL, host, Origin, base-site parameterFix the configuration variant, not a global rule.

A valid credentialed response has a concrete shape:

http
Access-Control-Allow-Origin: https://storefront.example.test
Access-Control-Allow-Credentials: true
Vary: Origin

Do not answer a CORS failure by adding * everywhere. SAP KBAs 3089617 and 3125076 cover Commerce CORS behavior, including the restriction on wildcard origins with credentials. KBA 3278460 covers a useful counterexample: a front-end “CORS” symptom can be a 400 response caused by headers that are too large.

Handle SSR 403s as an API policy problem#

If SSR receives a global “not authorized” error but client-side browser requests work, do not open a protected endpoint to the world. The Node SSR request originates from the JavaScript application infrastructure and can be blocked by Commerce API filters, CDN security, or IP restrictions even when a customer browser is allowed.

Use the following escalation packet:

  1. Route, timestamp, base site, API host, and Node/Commerce correlation ID.
  2. Exact HTTP status and sanitized response body; no cookie/token headers.
  3. Proof whether the call uses the expected public API endpoint or an explicitly configured private endpoint.
  4. Existing API filter/allow-list policy and the intended minimum source CIDR/service identity.
  5. Result of the same route from the browser and from SSR.

KBA 3103064 describes the private-endpoint filtering case. The safe resolution is to allow the approved SSR source through the private API policy with the platform/SAP support process, not to switch a restricted API to Deny None or make it public. The SSR optimization reference also calls out API/CDN allow-list checks for server-rendered global authorization errors.

Keep SmartEdit and external domains in the release checklist#

SmartEdit uses an iframe/preview context. A storefront response that sends X-Frame-Options: DENY can work for shoppers but fail in SmartEdit. KBA 3334671 documents that failure mode. Frame policy is a security control, so scope any exception to the required preview integration; do not relax clickjacking protections globally because an editor needs a page rendered.

External storefront domains add DNS/TLS/origin, CORS, cookie, and redirect variants. Test them as first-class production routes:

text
https://www.brand.example          -> public shopper origin
https://preview.brand.example      -> authorized preview/SmartEdit origin if required
https://api.brand.example          -> public API origin selected by JS App runtime config

For each host, test an anonymous PDP/PLP, an account/checkout route, one CMS change/preview workflow, CSP/frame headers, cookie SameSite behavior, and the exact CORS Origin. Do not assume a configuration that works through the platform’s default domain transfers unchanged to a vanity domain.

Release workflow: verify the deployed artifact, not the intent#

Use this deployment gate before promoting an environment:

StepVerificationFailure it prevents
1. Inspect source contractManifest name/path, source-vs-prebuilt choice, package manager lock file, build/build:ssr scriptsCCv2 discovers or builds the wrong thing.
2. Build locally/CIExecute the same application script that the selected deployment model requiresSSR-only bundle/server failure hidden by a CSR build.
3. Inspect artifactLocate dist/<app-name>/index.html, browser assets, and SSR output if enabledStale/misplaced artifact.
4. Inspect served HTMLConfirm the non-empty resolved occ-backend-base-url meta valuePlaceholder not replaced or hard-coded config overrides it.
5. Test both callersBrowser CSR Network trace and SSR-to-OCC trace use intended host/siteBrowser/Node endpoint divergence.
6. Test transport policyOPTIONS/GET probes, API filter decision, external domain/SmartEdit checksCORS, 403, frame-policy, or header-size failures after promotion.
7. Test deployment modeBlue/green/canary preview uses the expected injected endpointHard-coded endpoint bypasses deployment routing.
8. Record evidenceBuild ID, artifact checksum/version marker, URLs, configs (redacted), trace IDs, ownerUnrepeatable “it worked once” release claims.

The expected outcome is boring: one reproducible artifact, one intentional runtime endpoint rule, one exact transport policy, and equal evidence for browser and SSR behavior. If any of those differ, the deployment is not ready to hide behind a successful build status.