Vercel Integration Guide for SAP Composable Storefront

Vercel's frontend cloud gives developers the workflows and infrastructure to build faster with SAP Composable Storefront. Within minutes, you can host, deploy at the edge, and iterate on all of your Composable Storefronts in an environment adapted to maximize the developer experience and provide blazing fast speeds for your sites.

Before getting started, ensure that your development environment meets the requirements described in Front End Development Requirements, and that you have access to the Repository Based Shipment Channel (RBSC), as described in Installing Composable Storefront Libraries from the Repository Based Shipment Channel.

The following steps leverage ready-made libraries and packages for running Composable Storefront.

1. Verify that you have the front end development requirements as well as access to the RBSC.

2. Create a new Angular app named sap-store without routing and using SCSS styling:

ng new sap-store --style=scss --routing=false

3. For local development, ensure that you have an .npmrc configuration file in the project directory that references the SAP NPM registry.  Set _auth to a local NPM_TOKEN environment variable containing the access token for downloading Composable Storefront libraries. The following is an example of the .npmrc file:

.npmrc
@spartacus:registry=https://73554900100900004337.npmsrv.base.repositories.cloud.sap///73554900100900004337.npmsrv.base.repositories.cloud.sap/:_auth=${NPM_TOKEN}
always-auth=true

4. Install the latest official release of Composable Storefront using schematics. To see the schematics for the latest version of Composable Storefront, see Setting Up Your Project Using Schematics. The following is an example:

ng add @spartacus/[email protected]

5. Set baseUrl in src/app/spartacus/spartacus-configuration.module.ts to point to the SAP Commerce Cloud Server. By default, it is set to localhost:9002.

6. Update the context configuration in spartacus-configuration.module.ts with the following settings:

spartacus-configuration.module.ts
context: {
urlParameters: ['baseSite', 'language', 'currency'],
baseSite: ['electronics-spa', 'apparel-uk-spa'],
currency: ['USD', 'GBP'],
}

7. Build and run the project to verify the solution is working locally by running the following commands:

npm run build
npm run start

The following configuration steps describe how to deploy Composable Storefront to Vercel using client-side rendering. If you wish to deploy Composable Storefront using other rendering methods, such as server-side rendering, static site generation, and incremental static regeneration, see the Deploying Composable Storefront to Vercel with Additional Rendering Methods section.

1. Ensure that your local project is pushed to a repository on GitHub, Bitbucket, or GitLab.

2. Grant Vercel access to the repository to enable deployment.

3. Create a new Vercel project and choose the option to "Import from an Existing Repository".

4. During the project configuration in Vercel, make sure to include the NPM_TOKEN environment variable with valid NPM credentials. This allows Vercel to install the necessary SAP NPM packages.

5. Leave the remaining build settings at their default values.

vercel-sap-composable-storefront-deployment.png

6. Click on the "Deploy" button to initiate the deployment process.

In addition to CSR, Vercel supports various other rendering methods, such as server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR), which can be utilized with a hosted Composable Storefront application. Follow these configuration steps to deploy Composable Storefront on Vercel using these rendering methods.

1. Install the Composable Storefront packages using the ssr flag. The following is an example: ng add @spartacus/[email protected] --ssr

Ensure the latest version of Composable Storefront is specified when you run this command.

2. Ensure that the configuration settings in spartacus-configuration.module.ts are not overridden.

3. Open server.ts and update the ngExpressEngine rendering strategy to be ALWAYS_SSR. Modify the code as shown below:

const ngExpressEngine = NgExpressEngineDecorator.get(engine, {
renderingStrategyResolver: (req) => {
return RenderingStrategy.ALWAYS_SSR;
},
});

4. Run npm run build:ssr followed by npm run start:ssr to verify locally that the Angular application is running via SSR.

The following steps leverage Vercel’s Build Output API to add SSG, SSR, and ISR functionality to Composable Storefront and make it capable of running on Vercel. The Build Output API is a file-system-based specification for a directory structure that can produce a Vercel deployment.

Engineering teams can output this directory structure from the application’s build command, so that the Angular application may use all of the Vercel platform features. The following steps walk through how to implement this.

1. Create a JavaScript file named vercel-output.js at the root of the Composable Storefront application. This file is a script for outputting the JavaScript bundles to a location where Vercel can create serverless functions for running the application.

2. Paste in the following JavaScript code to vercel-output.js. This is the script that will run and perform several actions necessary for running on Vercel:

  • It creates a new .vercel/output directory containing all necessary bundled JavaScript and assets.
  • From a pre-set example of the product detail pages (included in the below vercel-output.js file), it creates the necessary serverless functions (.func files) for rendering a given page via SSG, SSR, and ISR.
  • It creates a build config.json file that configures the behavior of a deployment on Vercel.

This script uses example SSG and ISR routes at the top of the file that need to be specified in order for Vercel to use the correct rendering method for the associated URL. These routes should be replaced with the desired routes in the Composable Storefront application based on its rendering requirements.

const { lstatSync, readdirSync, copyFileSync, writeFileSync, mkdirSync } = require("fs");
const { dirname } = require("path");
const OUT_DIR = ".vercel/output";
const PROJECT_DIST = "dist/sap-store";
// This is the array of pages that are prerendered, specified in prerender-routes.txt
// Updates to these pages require a Vercel deployment
const SSG_PAGES = [
{
route: "/electronics-spa/en/USD/product/1934398/HDR-XR105E$",
staticHTML: "/product/1934398/HDR-XR105E/index.html",
},
{
route: "/electronics-spa/en/USD/product/1986316/LEGRIA%20HF%20S100",
staticHTML: "/product/1986316/LEGRIA%20HF%20S100/index.html",
},
{
route: "/electronics-spa/en/USD/product/1432722/Gigashot%20K80H",
staticHTML: "/product/1432722/Gigashot%20K80H/index.html",
},
{
route: "/electronics-spa/en/USD/product/1776948/Camileo%20S10%20EU",
staticHTML: "/product/1776948/Camileo%20S10%20EU/index.html",
},
{
route: "/electronics-spa/en/USD/product/1934406/HDR-CX105E%20%20Red",
staticHTML: "/product/1934406/HDR-CX105E%20%20Red/index.html",
},
{
route: "/electronics-spa/en/USD/product/1776947/Camileo%20H20%20EU",
staticHTML: "/product/1776947/Camileo%20H20%20EU/index.html",
}
]
// This is the array of pages that leverage Incremental Static Regeneration (ISR)
// The fallbackHTML is the path to the static HTML file that will be used for the initial render
// Subsequent renders will be generated by the ISR function and updated on a rolling interval (60 seconds)
const ISR_PAGES = [
{
id: "home",
route: "/electronics-spa/en/USD/",
fallbackHTML: "../static/index.html",
},
{
id: "nv10",
route: "/electronics-spa/en/USD/product/553637/NV10",
fallbackHTML: "../static/product/553637/NV10/index.html",
},
{
id: "dsc-n1",
route: "/electronics-spa/en/USD/product/358639/DSC-N1",
fallbackHTML: "../static/product/358639/DSC-N1/index.html",
}
]
// Utility function for writing content to a file
function write(file, data) {
try {
mkdirSync(dirname(file), { recursive: true });
} catch {}
writeFileSync(file, data);
}
// Utility function for copying files from one directory to another
function copyFiles(source, target) {
const files = readdirSync(source);
for (const file of files) {
const curSource = `${source}/${file}`;
if (lstatSync(curSource).isDirectory()) {
mkdirSync(`${target}/${file}`, { recursive: true });
copyFiles(curSource, `${target}/${file}`);
} else {
copyFileSync(curSource, `${target}/${file}`);
}
}
}
// Utility function that will create the SSR function and copy all files to the Vercel output folder
function createSSRFunction() {
const fn_dir = `${OUT_DIR}/functions/ssr.func`;
write(
`${fn_dir}/.vc-config.json`,
JSON.stringify({
runtime: "nodejs18.x",
handler: "index.js",
launcherType: "Nodejs",
})
);
copyFiles(`${PROJECT_DIST}/server`, fn_dir);
write(`${fn_dir}/index.js`, `module.exports = require("./main.js").app();`);
// static files also need to be copied to the function dir because the server runtime uses them
mkdirSync(`${fn_dir}/${PROJECT_DIST}/browser`, { recursive: true });
copyFiles(`${PROJECT_DIST}/browser`, `${fn_dir}/${PROJECT_DIST}/browser`);
}
// Utility function that will create the ISR function and copy all files to the Vercel output folder
function createISRFunction(name, group, fallback) {
const funcDir = `${OUT_DIR}/functions/${name}.func`;
write(
`${funcDir}/.vc-config.json`,
JSON.stringify({
runtime: "nodejs18.x",
handler: "index.js",
launcherType: "Nodejs",
})
);
copyFiles(`${PROJECT_DIST}/server`, funcDir);
write(`${funcDir}/index.js`, `module.exports = require("./main.js").app();`);
// static files also need to be copied to the function dir because the server runtime uses them
mkdirSync(`${funcDir}/${PROJECT_DIST}/browser`, { recursive: true });
copyFiles(`${PROJECT_DIST}/browser`, `${funcDir}/${PROJECT_DIST}/browser`);
// Create prerender config json file
write(
`${OUT_DIR}/functions/${name}.prerender-config.json`,
JSON.stringify({
// For this example, we are hardcoding the revalidation interval to 60 seconds
expiration: 60,
group,
allowQuery: ["__pathname"],
passQuery: true,
fallback,
})
);
}
// Create the static Vercel folder
mkdirSync(`${OUT_DIR}/static`, { recursive: true });
// Copy the static files to the Vercel folder
copyFiles(`${PROJECT_DIST}/browser`, `${OUT_DIR}/static`);
// Create the SSR serverless function responsible for all pages that are not SSG or ISR
createSSRFunction();
// Create an ISR serverless function for each specified ISR page with specified fallback HTML
ISR_PAGES.forEach((page, i) => {
createISRFunction(`isr-func-${page.id}`, ++i, page.fallbackHTML);
});
// Write the Vercel Build Output config file.
write(
`${OUT_DIR}/config.json`,
JSON.stringify({
version: 1,
routes: [
...SSG_PAGES.map(page => {
return {
src: `${page.route}$`,
dest: page.staticHTML,
}
}),
...ISR_PAGES.map(page => {
return {
src: `${page.route}$`,
dest: `/isr-func-${page.id}?__pathname=${page.route}`
}
}),
// Specify that SSR should be used for all other pages
{
src: "/.*",
dest: "/ssr"
},
],
})
);

3. Update the value of the variable PROJECT_DIST in the vercel-output.js script with the existing dist output directory path of your Composable Storefront application.

4. Update server.ts to handle the __pathname query parameter used by the ISR serverless functions, as follows:

server.ts
server.get('*', (req, res) => {
if (req.url) {
const [path, search] = req.url.split('?');
const params = new URLSearchParams(search);
const pathname = params.get('__pathname');
if (pathname) {
params.delete('__pathname');
req.url = pathname;
}
}
res.render(indexHtml, {
req,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
});
});

5. Create a prerender-routes.txt file at the root of the application (same level as package.json) and insert a route per line for every SSG and ISR route in the application. This file will be specified in angular.json in the following step to prerender the necessary pages. In this guide, the following is specified in prerender-routes.txt.

prerender-routes.txt
/product/1986316/LEGRIA%20HF%20S100
/product/1432722/Gigashot%20K80H
/product/1776948/Camileo%20S10%20EU
/product/1934398/HDR-XR105E
/product/1934406/HDR-CX105E%20%20Red
/product/1776947/Camileo%20H20%20EU
/product/358639/DSC-N1
/product/553637/NV10
/

6. Update the prerender section in angular.json and set the routesFile value to prerender-routes.txt, as follows:

"options": {
"routesFile": "prerender-routes.txt"
}

7. Update the build and start commands in Vercel to run an SSR build, prerender the configured routes, then run the Vercel output script, as follows:

package.json
build: "npm run build:ssr && npm run prerender && node ./vercel-output.js",
start: "ng serve:ssr"

8. Commit your changes and deploy the application to Vercel.

Following the build and deployment, Vercel will use SSG for the URLs specified in the SSG_PAGES array and ISR with a fallback for the URLs specified in the ISR_PAGES array within the Vercel output script. Initial requests to the ISR pages will display the prerendered version of the HTML and subsequent requests will see updates on a 60 second revalidation interval. SSR is leveraged for all other routes on the application.

In this guide, you have navigated through the process of configuring SAP Composable Storefront to run on Vercel. The application now uses Angular Universal for providing SSR and with the Vercel Build Output API it is capable of rendering pages with SSR, SSG, and ISR. These rendering methods can be leveraged for providing performance and SEO benefits without compromising on the end user experience.

Couldn't find the guide you need?