Vue Storefront is now Alokai! Learn More
Caching API responses

Caching API responses

Modern CDNs can cache API responses, significantly reducing the load on your middleware and improving the performance of your application, even if the HTML itself is not cached. Please note, that due to the nature of the CDN cache, only the traffic between the browser and the middleware will be cached. The requests sent during the SSR process will not be cached.

Requirements

@vue-storefront/next in version 4.0.0 or higher

@vue-storefront/sdk in version 3.1.0 or higher

How to enable caching API responses

Set Cache-Control header in the middleware

1

Setup the extension for all the integrations that you want to cache.

// apps/storefront-middleware/integrations/<integration>/extensions/cdn.ts

import middlewareHeaders from '@vsf-enterprise/middleware-headers';

const DEFAULT_MIDDLEWARE_TTL = 60 * 5;
const cacheControl =
  process.env.CACHE_CONTROL ||
  `public, max-age=0, s-maxage=${DEFAULT_MIDDLEWARE_TTL}, must-revalidate`;

export const cdnExtension = middlewareHeaders({
  cacheControl,
  isNamespaced: false,
  methods: {}, // will use default cache control configuration
});

2

Re-export the newly added extension in the barrel file

`apps/storefront-middleware/integrations/<integration>/extensions/index.ts`;

export * from './unified';
export * from './multistore';
export * from './cdn'; 

3

Plug the extension into your integration:

// apps/storefront-middleware/integrations/<integration>/config.ts

import { multistoreExtension, unifiedApiExtension } from "./extensions"; import { cdnExtension, multistoreExtension, unifiedApiExtension } from "./extensions"; ...

  extensions: (extensions: ApiClientExtension[]) => [
    ...extensions,
    unifiedApiExtension,
    cdnExtension,     ...(IS_MULTISTORE_ENABLED === "true" ? [multistoreExtension] : []),
  ],

Adjusting CI/CD

The CI/CD pipeline must be adjusted to pass the git commit hash as a cache-busting ID to the Docker image of the frontend.

Without this step, the cache will not be invalidated after deploying a new version of the middleware. This may result in the new frontend version receiving API responses from the outdated middleware version, potentially causing the website to crash until the cache is invalidated or considered old.

1

If you use github actions for your CD, ensure that you use version v4.4.0 or higher of the build-frontend action.

# .github/workflows/continuous-delivery.yml

jobs:
  build-frontend:
    # ...
    steps:
      # ...
      - name: Build
        uses: vuestorefront/storefront-deployment/build-frontend@v4.4.0

The action will, by default, set values of NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID and NUXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID based on github.sha value.

You can manually set the source of the cache busting ID by changing the value of the input.version key in the deployment process.

# .github/workflows/continuous-delivery.yml

jobs:
  build-frontend:
    # ...
    steps:
      # ...
      - name: Build
        uses: vuestorefront/storefront-deployment/build-frontend@v4.3.0
        with:
          version: ${{ secrets.APP_VERSION }}          # ...

2

When the Dockerfile is executed, Docker remembers the result of each action. If the result of an action is the same as the previous run, Docker reuses the cached result. This is called Docker cache. If the Dockerfile cache is invalidated, Docker will rerun the action, increasing the build time.

Since the CDN cache-busting value changes with every commit, we don't want to excessively invalidate the Docker cache. Therefore, this value is passed just before the application startup, which is handled by the vue-storefront.sh script.

Adjusting your storefront

Enabling runtime environment variables

  1. Install next-runtime-env, this package allows NextJS to use runtime environment variables. NextJS allows only for environment variables that are bundled together with the rest of the code, but the cdn cache busting id is being set after the application is built.
yarn add next-runtime-env
  1. Updade the next.config.ts
// apps/unified-storefront-nextjs/next.config.ts

const { configureRuntimeEnv } = require('next-runtime-env/build/configure'); 
/**
+ * The function generates the `__ENV.js` during startup file that contains the runtime environment variables
+*/
configureRuntimeEnv(); 
  1. Update SDK config and add cdn cache busting id and pass the default request config to the middleware for the unified methods.
// apps/unified-storefront-nextjs/sdk/options.ts

import type { CreateSdkOptions } from '@vue-storefront/next';
import { env } from 'next-runtime-env';

export function getSdkOptions() {
  // the env function uses process.env while on the server side, and the __ENV.js file on the client side
  const cdnCacheBustingId = env('NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID') ?? 'no-cache-busting-id-set';   // ...

  const options: CreateSdkOptions = {
    middleware: {
      // ...
      cdnCacheBustingId,     },
  };

  return options;
// apps/unified-storefront-nextjs/sdk/config.ts

...
export default defineSdkConfig(({ buildModule, middlewareModule, getRequestHeaders, config }) => ({
  unified: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${config.middlewareUrl}/commerce/unified`,
    defaultRequestConfig: {
      headers: getRequestHeaders(),
    },
   // The CDN Cache Busting ID changes every deployment. It's used to invalidate the old Middleware cache when a new version is being deployed
   cdnCacheBustingId: config.cdnCacheBustingId,
   // Default methods config that configures which methods should be sent via GET requests
   methodsRequestConfig: config.defaultMethodsRequestConfig.unifiedCommerce.middlewareModule
  }),

Keeping custom endpoints cacheable

If you define any new endpoints or want to customize the cache control for the existing ones, you can do it by following the steps below.

1

Define cache control for the new endpoint in the middleware

// apps/storefront-middleware/integrations/<integration>/extensions/cdn.ts
import middlewareHeaders from '@vsf-enterprise/middleware-headers';

const DEFAULT_MIDDLEWARE_TTL = 60 * 5;
const cacheControl =
  process.env.CACHE_CONTROL ||
  `public, max-age=0, s-maxage=${DEFAULT_MIDDLEWARE_TTL}, must-revalidate`;

export const cdnExtension = middlewareHeaders({
  cacheControl,
  isNamespaced: false,
  methods: {
    myNewEcommerceEndpoint: {
      cacheControl: `public, max-age=0, s-maxage=${60 * 15}, must-revalidate`,
    },
  },
});

2

Tell the SDK to send the request via GET method. In SDK config file.

// apps/storefront-unified-nextjs/sdk/config.ts

export function getSdkConfig() {
  return defineSdkConfig(
    ({ buildModule, config, getRequestHeaders, middlewareModule }) => ({
      commerce: buildModule(middlewareModule<CommerceEndpoints>, {
        // Default methods config that configures which methods should be sent via GET requests
        methodsRequestConfig: {
          ...config.defaultMethodsRequestConfig.unifiedCommerce
            .middlewareModule,
          myNewEcommerceEndpoint: {
            method: 'GET',
          },
        },
      }),
    })
  );
}

You should not cache endpoints that are mutations, respond with sensitive information, or return different data for each user.

Caching mutation endpoints would prevent mutations from being executed. Caching endpoints that respond with sensitive information can pose security risks, as cached data might be accessed by unauthorized users. Endpoints that return different data for each user should not be cached to prevent users from seeing data that belongs to someone else, which can lead to privacy violations and incorrect application behavior.

Validating if we did everything correctly

1

Run the project. If you are using Next.js, then you have to run it in a prod mode. So yarn build && yarn start from within the frontend’s directory apps/storefront-unified-nextjs. The Middleware can be executed in dev mode so yarn dev from within apps/storefront-middleware will work.

2

Open the network tab of your browser’s dev tools

3

Verify that the selected set of requests to the middleware are sent as GET requests. The Cache-Control header should be attached to the response (i.e. getProductDetails is one of the default requests that is usually cached, it is usually triggered on the product details page ). The Cache-Control header should contain the value that was set by you. If you didn't set any specific value, then the default value for s-maxage will be non-zero.