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