sure,
const { CustomCacheKey } = require('@layer0/core/router')
/**
* 24 hours
*/
const PAGE_TTL = 60 * 60 * 24
const IMAGE_TTL = 60 * 60 * 24
/**
* 10 years
*/
const FAR_FUTURE_TTL = 60 * 60 * 24 * 365 * 10
const SERVICE_WORKER = 60 * 60 * 24 * 365
/**
* 2 weeks
*/
const COUNTRIES_TTL = 60 * 60 * 24 * 7 * 2
/**
* One week
*/
const ONE_WEEK_TTL = 60 * 60 * 24 * 7
/**
* The custom cache key for all SSR and API responses. Here we ignore all
* query parameters except ones that we know our app specifically handles. This
* prevents cache fragmentation due to unexpected query parameters added in links
* from 3rd parties.
*/
const key = new CustomCacheKey()
.excludeAllQueryParametersExcept(
'q',
'sort',
'filters',
'color',
'size',
'amp',
'page',
'type',
'productId',
'productIds',
'category',
'token',
'endpoint'
)
.addCookie('x-xdn-geo-country-code')
.addCookie('countryCode')
.addCookie('xRsfUserLevel')
.addCookie('xRsfMsrpStatus')
const imagesCustomCacheKeys = new CustomCacheKey().excludeAllQueryParametersExcept(
'cache',
'width',
'height',
'quality',
'format'
)
module.exports = {
FAR_FUTURE_TTL,
PAGE_TTL,
IMAGE_TTL,
key,
/**
* The cache config for all server side rendered pages
*/
SSR: {
key,
browser: {
maxAgeSeconds: 0,
},
edge: {
forcePrivateCaching: true,
maxAgeSeconds: PAGE_TTL,
staleWhileRevalidateSeconds: PAGE_TTL,
},
},
/**
* The cache config for static assets
*/
STATIC_ASSETS: {
key,
browser: {
maxAgeSeconds: ONE_WEEK_TTL,
},
edge: {
forcePrivateCaching: true,
maxAgeSeconds: ONE_WEEK_TTL,
staleWhileRevalidateSeconds: 60 * 60 * 24,
},
},
/**
* The cache config for all server side rendered pages
*/
API: {
key,
browser: {
maxAgeSeconds: 0,
serviceWorkerSeconds: PAGE_TTL,
},
edge: {
forcePrivateCaching: true,
maxAgeSeconds: PAGE_TTL,
staleWhileRevalidateSeconds: PAGE_TTL,
},
},
/**
* The cache config for all images of scene7
*/
IMAGE: {
key: imagesCustomCacheKeys,
browser: {
maxAgeSeconds: 0,
serviceWorkerSeconds: IMAGE_TTL,
},
edge: {
forcePrivateCaching: true,
maxAgeSeconds: IMAGE_TTL,
staleWhileRevalidateSeconds: IMAGE_TTL,
},
},
/**
* Cache config for static assets that never change and thus can be cached
* at the edge and in the browser forever.
*/
FAR_FUTURE: {
key,
browser: {
maxAgeSeconds: FAR_FUTURE_TTL,
},
edge: {
forcePrivateCaching: true,
maxAgeSeconds: FAR_FUTURE_TTL,
staleWhileRevalidateSeconds: 60 * 60 * 24,
},
},
SERVICE_WORKER: {
key,
browser: {
maxAgeSeconds: SERVICE_WORKER,
},
edge: {
maxAgeSeconds: SERVICE_WORKER,
staleWhileRevalidateSeconds: 60 * 60 * 24,
},
},
NO_CACHE: {
browser: false,
edge: false,
},
/**
* The cache config for shipping address countries
*/
COUNTRIES: {
key,
browser: {
maxAgeSeconds: 0,
serviceWorkerSeconds: COUNTRIES_TTL,
},
edge: {
maxAgeSeconds: COUNTRIES_TTL,
staleWhileRevalidateSeconds: COUNTRIES_TTL,
},
},
/**
* Creates a route handler that caches based on the specified config
* @param {Object} config A config for @layer0/core's cache function
* @return {Function} a route handler
*/
cacheResponse: config => ({ cache }) => cache(config),
}
and routes
const { Router } = require('@layer0/core/router')
const { nextRoutes, renderNextPage } = require('@layer0/next')
const {
API,
SSR,
IMAGE,
NO_CACHE,
SERVICE_WORKER,
COUNTRIES,
STATIC_ASSETS,
cacheResponse,
} = require('./cache')
const prerenderRequests = require('./layer0/prerenderRequests')
function checkCacheRules(res) {
if (res?.request?.headers?.referer?.match(/token=/)) {
return res.cache(NO_CACHE)
}
if (res?.request?.query?.token?.match(/.+/)) {
return res.cache(NO_CACHE)
}
if (res?.request?.url?.includes('/api')) {
return res.cache(API)
}
return res.cache(SSR)
}
module.exports = () => {
const pwaRouter = new Router()
pwaRouter
.prerender(prerenderRequests)
.match('/service-worker.js', async ({ cache, serveStatic }) => {
cache(SERVICE_WORKER)
await serveStatic('.next/static/service-worker.js')
})
pwaRouter
.match(
{
path: '(.*)',
headers: { referer: /token=/ },
},
cacheResponse(NO_CACHE)
)
.match(
{
path: '(.*)',
query: { token: /.+/ },
},
cacheResponse(NO_CACHE)
)
.match('/api/p/:productId*', cacheResponse(API))
.match('/api/p/:productId*/data', cacheResponse(API))
.match('/p/:productId*', cacheResponse(SSR))
.match('/api/c/:categorySlug*', cacheResponse(API))
.match('/c/:categorySlug*', cacheResponse(SSR))
.match('/api/sign-in', cacheResponse(NO_CACHE))
.match('/api/signOut', cacheResponse(NO_CACHE))
.match('/api/signUp', cacheResponse(NO_CACHE))
.match('/api/session', cacheResponse(NO_CACHE))
.match('/api/suggestions', cacheResponse(API))
.match('/api/user/:id*', cacheResponse(NO_CACHE))
.match('/api/subscribe', cacheResponse(NO_CACHE))
.match('/api/getStores', cacheResponse(API))
.match('/api/cart/getEntries', cacheResponse(NO_CACHE))
.match('/api/cart/getCurrent', cacheResponse(NO_CACHE))
.match('/api/cart/withPromoCode', cacheResponse(NO_CACHE))
.match('/api/orders/getList', cacheResponse(NO_CACHE))
.match('/api/s7image/:path*', cacheResponse(IMAGE))
.match('/theme', cacheResponse(SSR))
.match('/api/theme', cacheResponse(API))
.match('/api/widgets/branded-products', cacheResponse(API))
.match('/api/widgets/cart-dont-forget', cacheResponse(API))
.match('/api/widgets/cart-recommended-products', cacheResponse(API))
.match('/api/widgets/cart-style-with', cacheResponse(API))
.match('/api/widgets/frequently-viewed-together', cacheResponse(API))
.match('/api/widgets/more-like-this', cacheResponse(API))
.match('/api/widgets/our-branded-products', cacheResponse(API))
.match('/api/widgets/people-buy', cacheResponse(API))
.match('/api/widgets/product-complete-room', cacheResponse(API))
.match('/api/widgets/product-perfect-proportions', cacheResponse(API))
.match('/api/widgets/related-searches', cacheResponse(API))
.match('/api/widgets/style-with', cacheResponse(API))
.match('/api/widgets/top-reviewed-products', cacheResponse(API))
.match('/api/widgets/trending-products', cacheResponse(API))
.match('/api/widgets/viewed-together', cacheResponse(API))
.match('/api/widgets/you-may-also-like', cacheResponse(API))
.match('/api/widgets/you-may-also-like-category', cacheResponse(API))
.match('/api/countries', cacheResponse(COUNTRIES))
.match('/api/helpers/categories', cacheResponse(API))
.match('/:file*.js', cacheResponse(STATIC_ASSETS))
.match('/:file*.css', cacheResponse(STATIC_ASSETS))
.match('/:file*.aif', cacheResponse(STATIC_ASSETS))
.match('/:file*.bmp', cacheResponse(STATIC_ASSETS))
.match('/:file*.doc', cacheResponse(STATIC_ASSETS))
.match('/:file*.gif', cacheResponse(STATIC_ASSETS))
.match('/:file*.jpeg', cacheResponse(STATIC_ASSETS))
.match('/:file*.jpg', cacheResponse(STATIC_ASSETS))
.match('/:file*.png', cacheResponse(STATIC_ASSETS))
.match('/:file*.otf', cacheResponse(STATIC_ASSETS))
.match('/:file*.mov', cacheResponse(STATIC_ASSETS))
.match('/:file*.pdf', cacheResponse(STATIC_ASSETS))
.match('/:file*.txt', cacheResponse(STATIC_ASSETS))
.match('/:file*.wbmp', cacheResponse(STATIC_ASSETS))
.match('/:file*.tif', cacheResponse(STATIC_ASSETS))
.match('/:file*.tiff', cacheResponse(STATIC_ASSETS))
.match('/:file*.ttf', cacheResponse(STATIC_ASSETS))
.match('/:file*.eot', cacheResponse(STATIC_ASSETS))
.match('/:file*.woff', cacheResponse(STATIC_ASSETS))
.match('/:file*.woff2', cacheResponse(STATIC_ASSETS))
.match('/:file*.svg', cacheResponse(STATIC_ASSETS))
pwaRouter
.use(nextRoutes)
.match('/(.*)', res => {
checkCacheRules(res)
if (res?.request?.url?.includes('/api')) {
return renderNextPage('/api/proxy/[...params]', res, { url: res?.request?.url })
}
return renderNextPage('/proxy/[...params]', res, { url: res?.request?.url })
})
.fallback(({ proxy }) => proxy('legacy'))
// For Split testing to legacy
const legacyRouter = new Router()
.match('/:file*.js', cacheResponse(STATIC_ASSETS))
.match('/:file*.css', cacheResponse(STATIC_ASSETS))
.match('/:file*.aif', cacheResponse(STATIC_ASSETS))
.match('/:file*.bmp', cacheResponse(STATIC_ASSETS))
.match('/:file*.doc', cacheResponse(STATIC_ASSETS))
.match('/:file*.gif', cacheResponse(STATIC_ASSETS))
.match('/:file*.jpeg', cacheResponse(STATIC_ASSETS))
.match('/:file*.jpg', cacheResponse(STATIC_ASSETS))
.match('/:file*.png', cacheResponse(STATIC_ASSETS))
.match('/:file*.otf', cacheResponse(STATIC_ASSETS))
.match('/:file*.mov', cacheResponse(STATIC_ASSETS))
.match('/:file*.pdf', cacheResponse(STATIC_ASSETS))
.match('/:file*.txt', cacheResponse(STATIC_ASSETS))
.match('/:file*.wbmp', cacheResponse(STATIC_ASSETS))
.match('/:file*.tif', cacheResponse(STATIC_ASSETS))
.match('/:file*.tiff', cacheResponse(STATIC_ASSETS))
.match('/:file*.ttf', cacheResponse(STATIC_ASSETS))
.match('/:file*.eot', cacheResponse(STATIC_ASSETS))
.match('/:file*.woff', cacheResponse(STATIC_ASSETS))
.match('/:file*.woff2', cacheResponse(STATIC_ASSETS))
.match('/:file*.svg', cacheResponse(STATIC_ASSETS))
.fallback(async ({ proxy, cache }) => {
cache({
edge: false,
})
await proxy('legacy')
})
return new Router().destination('pwa', pwaRouter).destination('legacy', legacyRouter)
}