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
- 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
- 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();
- 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.