Last active
October 20, 2022 06:23
-
-
Save claus/5b7f7fd8b5aa7be4e6f731f7d202b1f5 to your computer and use it in GitHub Desktop.
Data Prefetching in Next.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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?'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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