1
0
Fork 0
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:
Sergio Xalambrí 2017-12-26 15:46:46 -05:00 committed by Tim Neutkens
parent 29c4035eb5
commit ffdf4e3228
5 changed files with 218 additions and 0 deletions

View 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.

View 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)
}
}
}

View 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"
}
}

View 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>
)
}
}

View 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>
)