Example Deployment with Vercel
This deployment example refers to the Vercel serverless deployment, formerly ZEIT Now.
Prerequisites
Before you get started, you need to have:
A commercetools account and a project.
In this example we are going to use the commercetools platform running on the Europe cloud region on Google Cloud.
A Vercel account
The Vercel CLI
We're going to deploy the application under the domain mc-examples-starter.vercel.app
.
The mc-examples-starter.vercel.app
domain is already in use. Pick a different one if you choose to deploy using Vercel.
Configuration
To start, we need to create an env.prod.json
file with the following JSON:
{"applicationName": "mc-examples-starter","frontendHost": "mc-examples-starter.vercel.app","mcApiUrl": "https://mc-api.europe-west1.gcp.commercetools.com","location": "gcp-eu","env": "production","cdnUrl": "http://mc-examples-starter.vercel.app","servedByProxy": true}
We also need a headers.prod.json
to configure the Content Security Policy to allow the required hostnames:
{"csp": {"script-src": ["mc-examples-starter.vercel.app"],"connect-src": ["mc-examples-starter.vercel.app","mc-api.europe-west1.gcp.commercetools.com","mc-api.commercetools.com"],"style-src": ["mc-examples-starter.vercel.app"]}}
To configure Vercel (formerly Zeit Now) deployments, we can use a vercel.json
file. It looks something like this:
{"version": 2,"public": true,"name": "mc-examples-starter.vercel.app","alias": "mc-examples-starter.vercel.app","regions": ["bru"],"builds": [{"src": "public/**","use": "@now/static"}],"routes": [{"src": "/(.*).(js.map|js|css|txt|html|png)","dest": "/public/$1.$2","headers": {"cache-control": "s-maxage=31536000,immutable"}},{"src": "/(.*)","dest": "/public/index.html","headers": {"Cache-Control": "no-cache"}}]}
Some fields may vary based on your setup and requirements, for example public
, regions
, etc.
However, that won't work just yet as the Custom Application does not have an index.html
after building the production bundles.
To make it work, we need to compile the application first.
Compile the application
The Merchant Center Custom Applications are available by default with a built-in HTTP server, which takes care of preparing the index.html
according to the env.json
and headers.json
configuration (see Runtime configuration).
To be able to deploy the Custom Application to the version 2 of the Vercel platform, the application needs to be configured and built statically.
This is possible using the compile-html
command.
mc-scripts compile-html
The command requires to provide the runtime configuration files so that the index.html
can be properly compiled.
mc-scripts compile-html \--headers=$(pwd)/headers.prod.json \--config=$(pwd)/env.prod.json \--use-local-assets
The --use-local-assets
option is required for the sake of this example. See Serving static assets.
The command above does what we need: it compiles the index.html
using the JavaScript bundle references (after running mc-scripts build
) and the runtime configuration. At this point the index.html
file is ready for production usage.
However, the Custom Application needs to instruct the User-Agent (the browser) to enforce certain security measures, using HTTP headers.
The HTTP headers are also compiled together with the index.html
, as they rely on the runtime configuration headers.json
.
Because of that, the now.json
file cannot be defined statically. Instead, it needs to be generated programmatically when the Custom Application is built and compiled.
To achieve that, we need to implement a transformer function.
Generate now.json
using a transformer function
The compile-html
command accepts an option transformer
which we can use to pass the filesystem path to our transformer function.
We assume that the transformer function is defined at the following location: ./config/transformer-now.js
.
mc-scripts compile-html \--headers=$(pwd)/headers.prod.json \--config=$(pwd)/env.prod.json \--use-local-assets \--transformer $(pwd)/config/transformer-now.js
The purpose of the transformer function is to generate the final now.json
given the compiled values passed to the function.
// Function signature using TypeScripttype TransformerFunctionOptions = {// The content of the `env.json` file.env: Json;// The compiled HTTP headers, including CSP (see `loadHeaders` from `@commercetools-frontend/mc-html-template`).headers: Json;// The final HTML content of the `index.html`.indexHtmlContent: string;}type TransformerFunction = (options: TransformerFunctionOptions) => void;
The main export of the file should be the transformer function.
module.exports = function transformer(options) {// ...}
With that in mind, we can implement the transformer function and write the now.json
config into the filesystem.
const fs = require('fs');const path = require('path');const rootPath = path.join(__dirname, '..');module.exports = function transformer({ headers }) {const config = {version: 2,public: true,name: 'mc-examples-starter.vercel.app',alias: 'mc-examples-starter.vercel.app',regions: ['bru'],builds: [{ src: 'public/**', use: '@now/static' },],routes: [{src: '/(.*).(js.map|js|css|txt|html|png)',dest: '/public/$1.$2',headers: { 'Cache-Control': 's-maxage=31536000,immutable' },},{src: '/(.*)',dest: '/public/index.html',headers: { 'Cache-Control': 'no-cache', ...headers },},],};fs.writeFileSync(path.join(rootPath, 'now.json'),JSON.stringify(config, null, 2),{ encoding: 'utf8' });};
Adding fallback routes
This step is optional and does not prevent the application to be used within the Merchant Center. However, it's recommended to do so to avoid unexpected behaviors in case the URL, where the Custom Application is hosted, is accessed directly.
Accessing the Custom Application directly at https://mc-examples-starter.vercel.app
won't work, as the application requires the user to log in and thus tries to redirect to the /login
route at the same domain.
To prevent that, we can add a dummy fallback route for the login|logout
routes. This is only meant to inform the user that the Custom Application cannot be used standalone.
module.exports = function fallbackRoute(request, response) {response.end('This is not a real route. If you are seeing this, you most likely are accessing the custom application\n' +'directly from the hosted domain. Instead, you need to access the custom application from within the Merchant Center\n' +'domain, as custom applications are served behind a proxy router.\n' +'To do so, you need to first register the custom application in Merchant Center > Settings > Custom Applications.');};
This route will be used as a serverless function:
const fs = require('fs');const path = require('path');const rootPath = path.join(__dirname, '..');module.exports = function transformer({ headers }) {const config = {version: 2,public: true,name: 'mc-examples-starter.vercel.app',alias: 'mc-examples-starter.vercel.app',regions: ['bru'],builds: [{ src: 'public/**', use: '@now/static' },{ src: 'config/fallback-route.js', use: '@now/node' },],routes: [{src: '/(.*).(js.map|js|css|txt|html|png)',dest: '/public/$1.$2',headers: { 'Cache-Control': 's-maxage=31536000,immutable' },},{ src: '/(login|logout)', dest: '/config/fallback-route.js' },{src: '/(.*)',dest: '/public/index.html',headers: { 'Cache-Control': 'no-cache', ...headers },},],};fs.writeFileSync(path.join(rootPath, 'now.json'),JSON.stringify(config, null, 2),{ encoding: 'utf8' });};
Deployment
Finally, we can trigger the deployment using the Vercel CLI:
yarn buildmc-scripts compile-html \--headers=$(pwd)/headers.prod.json \--config=$(pwd)/env.prod.json \--use-local-assets \--transformer $(pwd)/config/transformer-now.jsnow
Now you're ready to Register your Custom Application and start using it!