mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add with-data-prefetch example (#3420)
* Add with-data-prefetch example * Fix typos * Improve example code
This commit is contained in:
parent
29c4035eb5
commit
ffdf4e3228
47
examples/with-data-prefetch/README.md
Normal file
47
examples/with-data-prefetch/README.md
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-data-prefetch)
|
||||||
|
# Example app with prefetching data
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Using `create-next-app`
|
||||||
|
|
||||||
|
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -g create-next-app
|
||||||
|
create-next-app --example with-data-prefetch with-data-prefetch-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download manually
|
||||||
|
|
||||||
|
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-data-prefetch
|
||||||
|
cd with-data-prefetch
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
Next.js lets you prefetch the JS code of another page just adding the `prefetch` prop to `next/link`. This can help you avoid the download time a new page you know beforehand the user is most probably going to visit.
|
||||||
|
|
||||||
|
In the example we'll extend the `Link` component to also run the `getInitialProps` (if it's defined) of the new page and save the resulting props on cache. When the user visits the page it will load the props from cache and avoid any request.
|
||||||
|
|
||||||
|
It uses `sessionStorage` as cache but it could be replaced with any other more specialized system. Like IndexedDB or just an in-memory API with a better cache strategy to prune old cache and force new fetching.
|
||||||
|
|
||||||
|
> This example is based on the [ScaleAPI article explaining this same technique](https://www.scaleapi.com/blog/increasing-the-performance-of-dynamic-next-js-websites).
|
||||||
|
|
||||||
|
**Note**: it only works in production environment. In development Next.js just avoid doing the prefetch.
|
64
examples/with-data-prefetch/components/link.js
Normal file
64
examples/with-data-prefetch/components/link.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import Router from 'next/router'
|
||||||
|
import { execOnce, warn } from 'next/dist/lib/utils'
|
||||||
|
import exact from 'prop-types-exact'
|
||||||
|
import { format, resolve, parse } from 'url'
|
||||||
|
|
||||||
|
// extend default next/link to customize the prefetch behaviour
|
||||||
|
export default class LinkWithData extends Link {
|
||||||
|
// re defined Link propTypes to add `withData`
|
||||||
|
static propTypes = exact({
|
||||||
|
href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
|
as: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
prefetch: PropTypes.bool,
|
||||||
|
replace: PropTypes.bool,
|
||||||
|
shallow: PropTypes.bool,
|
||||||
|
passHref: PropTypes.bool,
|
||||||
|
scroll: PropTypes.bool,
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.element,
|
||||||
|
(props, propName) => {
|
||||||
|
const value = props[propName]
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
execOnce(warn)(`Warning: You're using a string directly inside <Link>. This usage has been deprecated. Please add an <a> tag as child of <Link>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
]).isRequired,
|
||||||
|
withData: PropTypes.bool // our custom prop
|
||||||
|
});
|
||||||
|
|
||||||
|
// our custom prefetch method
|
||||||
|
async prefetch () {
|
||||||
|
// if the prefetch prop is not defined or
|
||||||
|
// we're running server side do nothing
|
||||||
|
if (!this.props.prefetch) return
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
|
const url =
|
||||||
|
typeof this.props.href !== 'string'
|
||||||
|
? format(this.props.href)
|
||||||
|
: this.props.href
|
||||||
|
|
||||||
|
const { pathname } = window.location
|
||||||
|
|
||||||
|
const href = resolve(pathname, url)
|
||||||
|
|
||||||
|
const { query } =
|
||||||
|
typeof this.props.href !== 'string'
|
||||||
|
? this.props.href
|
||||||
|
: parse(url, true)
|
||||||
|
|
||||||
|
const Component = await Router.prefetch(href)
|
||||||
|
|
||||||
|
// if withData prop is defined, Component exists and has getInitialProps
|
||||||
|
// fetch the component props (the component should save it in cache)
|
||||||
|
if (this.props.withData && Component && Component.getInitialProps) {
|
||||||
|
const ctx = { pathname: href, query, isVirtualCall: true }
|
||||||
|
await Component.getInitialProps(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
examples/with-data-prefetch/package.json
Normal file
15
examples/with-data-prefetch/package.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isomorphic-unfetch": "^2.0.0",
|
||||||
|
"next": "^4.2.0",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"prop-types-exact": "^1.1.1",
|
||||||
|
"react": "^16.2.0",
|
||||||
|
"react-dom": "^16.2.0"
|
||||||
|
}
|
||||||
|
}
|
67
examples/with-data-prefetch/pages/article.js
Normal file
67
examples/with-data-prefetch/pages/article.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { Component } from 'react'
|
||||||
|
import fetch from 'isomorphic-unfetch'
|
||||||
|
import { format } from 'url'
|
||||||
|
|
||||||
|
export default class Article extends Component {
|
||||||
|
static async getInitialProps ({ req, query, pathname, isVirtualCall }) {
|
||||||
|
const url = format({ pathname, query })
|
||||||
|
|
||||||
|
// if we're not running server side
|
||||||
|
// get the props from sessionStorage using the pathname + query as key
|
||||||
|
// if we got something return it as an object
|
||||||
|
if (!req) {
|
||||||
|
const props = window.sessionStorage.getItem(url)
|
||||||
|
if (props) {
|
||||||
|
return JSON.parse(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch data as usual
|
||||||
|
const responses = await Promise.all([
|
||||||
|
fetch(`https://jsonplaceholder.typicode.com/posts/${query.id}`),
|
||||||
|
fetch(`https://jsonplaceholder.typicode.com/posts/${query.id}/comments`)
|
||||||
|
])
|
||||||
|
|
||||||
|
const [article, comments] = await Promise.all(
|
||||||
|
responses.map(response => response.json())
|
||||||
|
)
|
||||||
|
|
||||||
|
const user = await fetch(
|
||||||
|
`https://jsonplaceholder.typicode.com/users/${article.userId}`
|
||||||
|
).then(response => response.json())
|
||||||
|
|
||||||
|
const props = { article, comments, user }
|
||||||
|
|
||||||
|
// if the method is being called by our Link component
|
||||||
|
// save props on sessionStorage using the full url (pathname + query)
|
||||||
|
// as key and the serialized props as value
|
||||||
|
if (isVirtualCall) {
|
||||||
|
window.sessionStorage.setItem(url, JSON.stringify(props))
|
||||||
|
}
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { article, comments, user } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{article.title}</h1>
|
||||||
|
<div>
|
||||||
|
<a href={`mailto:${user.email}`}>{user.name}</a>
|
||||||
|
</div>
|
||||||
|
<p>{article.body}</p>
|
||||||
|
<ul>
|
||||||
|
{comments.map(comment => (
|
||||||
|
<li key={comment.id}>
|
||||||
|
{comment.body}
|
||||||
|
<br />
|
||||||
|
By <strong>{comment.name}</strong>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
25
examples/with-data-prefetch/pages/index.js
Normal file
25
examples/with-data-prefetch/pages/index.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import Link from '../components/link'
|
||||||
|
|
||||||
|
// we just render a list of 3 articles having 2 with prefetched data
|
||||||
|
export default () => (
|
||||||
|
<main>
|
||||||
|
<h1>Next.js - with data prefetch example</h1>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link href='/article?id=1' prefetch withData>
|
||||||
|
<a>Article 1</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href='/article?id=2' prefetch>
|
||||||
|
<a>Article 2</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href='/article?id=3' prefetch withData>
|
||||||
|
<a>Article 3</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
)
|
Loading…
Reference in a new issue