Skip to content

Instantly share code, notes, and snippets.

@claus
Last active October 20, 2022 06:23
Show Gist options
  • Save claus/5b7f7fd8b5aa7be4e6f731f7d202b1f5 to your computer and use it in GitHub Desktop.
Save claus/5b7f7fd8b5aa7be4e6f731f7d202b1f5 to your computer and use it in GitHub Desktop.
Data Prefetching in Next.js

Data Prefetching in Next.js

const { resolve, parse, format } = require('url');
const PropTypes = require('prop-types');
const nextRoutes = require('next-routes');
const NextLink = require('next/link').default;
const NextRouter = require('next/router').default;
class DataPrefetchLink extends NextLink {
prefetch() {
if (typeof window === 'undefined' || !this.props.prefetch) {
return;
}
const { pathname } = window.location;
const hrefString =
typeof this.props.href !== 'string'
? format(this.props.href)
: this.props.href;
const href = resolve(pathname, hrefString.replace(/\?$/, ''));
const { query } = parse(href, true);
return NextRouter.prefetch(href).then(Component => {
if (
this.props.withData &&
Component &&
Component.getInitialProps &&
typeof Component.getInitialProps === 'function'
) {
const ctx = {
asPath: this.props.as,
pathname: href,
query,
isVirtualCall: true,
};
return Component.getInitialProps(ctx).then(() => Component);
}
});
}
}
DataPrefetchLink.propTypes = {
withData: PropTypes.bool,
};
const routes = (module.exports = nextRoutes({ Link: DataPrefetchLink }));
routes
.add('landing', '/')
.add('portfolio', '/portfolio/:slug?');
import React from 'react';
import PropTypes from 'prop-types';
class SimpleContentfulCache {
store = {};
setEntry(pathname, props) {
return (this.store[pathname] = props);
}
getEntry(pathname, resolver) {
if (typeof this.store[pathname] !== 'undefined') {
return Promise.resolve(this.store[pathname]);
}
return resolver().then(results => {
return this.setEntry(pathname, results);
});
}
}
const cache = new SimpleContentfulCache();
const getDisplayName = Component => {
return Component.displayName || Component.name || 'Component';
}
// withCache HOC
// Component:
// must be a Next.js page component
// config:
// - cacheKey
// default: ctx.asPath
// string: ctx[cacheKey]
// function: cacheKey(ctx) { return some_key; }
const withCache = (Component, config = {}) => {
return class WithCache extends React.Component {
static propTypes = {
$cacheKey: PropTypes.string.isRequired,
$cachedProps: PropTypes.array.isRequired,
$isServerRendered: PropTypes.bool.isRequired,
};
static displayName = `WithCache(${getDisplayName(Component)})`;
static getInitialProps(ctx) {
const isServerRendered = !!ctx.req;
const cacheKey =
typeof config.cacheKey === 'function'
? config.cacheKey(ctx)
: typeof config.cacheKey === 'string'
? ctx[config.cacheKey]
: ctx.asPath;
const cachedPropsPromise = isServerRendered
? WithCache.$getCachedInitialProps(ctx)
: cache.getEntry(cacheKey, () => {
return WithCache.$getCachedInitialProps(ctx);
});
return cachedPropsPromise.then(cachedProps => {
return WithCache.$getInitialProps(ctx, cachedProps).then(
props => ({
...cachedProps,
...props,
$cacheKey: cacheKey,
$cachedProps: Object.keys(cachedProps),
$isServerRendered: isServerRendered,
})
);
});
}
static $getCachedInitialProps(ctx) {
return Component.getCachedInitialProps
? Component.getCachedInitialProps(ctx)
: Promise.resolve({});
}
static $getInitialProps(ctx, props) {
return Component.getInitialProps
? Component.getInitialProps(ctx, props)
: Promise.resolve({});
}
componentDidMount() {
const {
$cacheKey,
$cachedProps,
$isServerRendered,
...props
} = this.props;
if ($isServerRendered) {
cache.setEntry(
$cacheKey,
$cachedProps.reduce((res, cachedProp) => {
res[cachedProp] = props[cachedProp];
return res;
}, {})
);
}
}
render() {
const {
$cacheKey,
$cachedProps,
$isServerRendered,
...props
} = this.props;
return <Component {...props} />;
}
};
};
export default withCache;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment