Skip to content

Instantly share code, notes, and snippets.

@gleuch
Created November 18, 2020 19:16
Show Gist options
  • Save gleuch/0951496be0ae4f7eabfdccd4dda45ef5 to your computer and use it in GitHub Desktop.
Save gleuch/0951496be0ae4f7eabfdccd4dda45ef5 to your computer and use it in GitHub Desktop.
Cross-platform cacheable method for NextJS

NextJS Cacheable storage

Store data in redis (server-side) or sessionStorage (client-side) with expiry time (seconds) using NextJS.

Installation

Add redis and redis-mock (for testing): yarn add redis redis-mock

Copy cacheable.js and redis.js to lib/.

Make your calls with cacheable(key, expiration, callback).

  • key is a unique cache key for matching
  • expiration is seconds to persist cache value, e.g. 30 seconds will expire after 30 seconds
  • callback is your callback function to execute if cached result does not exist. The result will then be stored into cache.

Example

import cacheable from 'lib/cacheable.js';

export const fetchPage = async id => {
  const key = `pages:${id}`; // cache key
  const expiry = 60; // in seconds
  return cacheable(key, expiry, async () => {
    const resp = await axios.get(`https://data.ltd/pages/${id}.json`);
    return resp.body;
  });
};
const { getAsync, setexAsync } = process.browser || !process.env.REDIS_URL ? {} : require('lib/redis');
// Get cache value from sessionStorage and check expiration
const getLocalStorage = key => {
const cache = sessionStorage.getItem(key);
if (!cache) {
return null;
}
try {
const { expiry, value } = JSON.parse(cache);
if (Date.now() > expiry) {
sessionStorage.removeItem(key);
return null;
}
return value;
} catch (err) {
return null;
}
};
// Set expiring cche value to sessionStorage
const setExpiryLocalStorage = (key, ttl, value) => {
const result = { expiry: Date.now() + ttl * 1000, value };
return sessionStorage.setItem(key, JSON.stringify(result));
};
// Get cache value based on environmment options
const getCacheable = key => {
if (process.browser && window.sessionStorage) {
return getExpiryLocalStorage(key);
} else if (getAsync) {
return getAsync(key);
}
return null;
};
// Set expiring cache value based on environmment options
const setCacheable = (key, ttl, value) => {
if (process.browser) {
return setExpiryLocalStorage(key, ttl, value);
} else if (setexAsync) {
return setexAsync(key, ttl, value);
}
return null;
};
/* eslint-disable-next-line import/prefer-default-export */
export const cacheable = async (key, exp, fn) => {
try {
const cache = await getCacheable(key);
if (cache) {
return JSON.parse(cache);
}
} catch (err) {
// ...continue to re-eval
}
const result = await fn.call();
if (typeof result !== 'undefined') {
await setCacheable(key, exp, JSON.stringify(result));
}
return result;
};
/* eslint-disable-next-line import/no-dynamic-require */
const Redis = require(process.env.NODE_ENV === 'test' ? 'redis-mock' : 'redis');
// const Sentry = require('@sentry/node');
const { promisify } = require('util');
const redis = Redis.createClient(process.env.REDIS_URL);
// redis.on('error', err => {
// Sentry.addBreadcrumb({ message: 'Redis error' });
// Sentry.captureException(err);
// });
['get', 'setex', 'del'].forEach(n => {
redis[`${n}Async`] = promisify(redis[n]).bind(redis);
});
module.exports = redis;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment