Skip to content

Commit

Permalink
Add example on how to pass data through js api during SSR
Browse files Browse the repository at this point in the history
Requested in vercel#1117
  • Loading branch information
unregistered committed Jul 18, 2017
1 parent 00e2659 commit f4ee2a0
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 0 deletions.
48 changes: 48 additions & 0 deletions examples/pass-server-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/pass-server-data)

# Pass Server Data Directly to a Next.js Page during SSR

## How to use

Download the example [or clone the repo](https://github.com/zeit/next.js):

```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/pass-server-data
cd pass-server-data
```

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

If you already have a custom server which has local data (for instance cached data from an API call, or data read
from a file at startup) that you wish to make available in the Next.js page, you can pass that data in the query
parameter of `nextApp.render()`.

This is not the only way to pass data. You could also expose an endpoint and make a `fetch()` call to localhost, or you could
import server-side code with `eval` (necessary to prevent webpack from trying to package your server code). However both
solutions leave something to be desired in either performance or elegance.

This example shows the express server at `server.js` reading in a file at load time with static data (this could also have been
data cached from an API call) in `operations/get-item.js`. It has two routes: a home page, and an item page. The item page uses
data from the get-item operation, passed as a query parameter in `routes/item.js`.

We use this data in `pages/item.js` if rendered server-side, or make a fetch request if rendered client-side.

Take a look at the following files:

* server.js
* routes/item.js
* pages/item.js
* operations/get-item.js
5 changes: 5 additions & 0 deletions examples/pass-server-data/data/item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "Now",
"subtitle": "Realtime global deployments",
"seller": "Zeit"
}
12 changes: 12 additions & 0 deletions examples/pass-server-data/operations/get-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs')

This comment has been minimized.

Copy link
@atainter

atainter Jul 18, 2017

Is this file necessary? We can probably just throw this into the route for the sake of simplicity.

This comment has been minimized.

Copy link
@unregistered

unregistered Jul 18, 2017

Author Owner

I wanted to for demonstration, that way we could discuss the possibility of require-ing this file from the pages directory and why that wouldn't work

This comment has been minimized.

Copy link
@atainter

atainter Jul 18, 2017

👍


// In this case, data read from the fs, but it could also be a cached API result.
const data = fs.readFileSync('./data/item.json', 'utf8')
const parsedData = JSON.parse(data)

function getItem () {
console.log('Requested Item Data:', data)
return parsedData
}

module.exports = { getItem }
16 changes: 16 additions & 0 deletions examples/pass-server-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "pass-server-data",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"express": "^4.14.0",
"isomorphic-fetch": "^2.2.1",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
}
}
8 changes: 8 additions & 0 deletions examples/pass-server-data/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import Link from 'next/link'

export default () => (
<ul>
<li><Link href='/item'><a>View Item</a></Link></li>
</ul>
)
33 changes: 33 additions & 0 deletions examples/pass-server-data/pages/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Component} from 'react'
import Link from 'next/link'
import fetch from 'isomorphic-fetch'

export default class extends Component {
static async getInitialProps ({ req, query }) {
const isServer = !!req

console.log('getInitialProps called:', isServer ? 'server' : 'client')

if (isServer) {
// When being rendered server-side, we have access to our data in query that we put there in routes/item.js,
// saving us an http call. Note that if we were to try to require('../operations/get-item') here,
// it would result in a webpack error.
return { item: query.itemData }
} else {
// On the client, we should fetch the data remotely
const res = await fetch('http://localhost:3000/_data/item')
const json = await res.json()
return { item: json }
}
}

render () {
return (
<div className='item'>
<div><Link href='/'><a>Back Home</a></Link></div>
<h1>{this.props.item.title}</h1>
<h2>{this.props.item.subtitle} - {this.props.item.seller}</h2>
</div>
)
}
}
20 changes: 20 additions & 0 deletions examples/pass-server-data/routes/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const api = require('../operations/get-item')

// Returns the express handler function. Necessary so that we can get the nextApp instance.
function getItemRenderer (nextApp) {
return (req, res) => {
console.log('Express is handling request')
const itemData = api.getItem()
return nextApp.render(req, res, '/item', { itemData })

This comment has been minimized.

Copy link
@atainter

atainter Jul 18, 2017

Merge with req.query?

This comment has been minimized.

Copy link
@unregistered

unregistered Jul 18, 2017

Author Owner

I didn't want to let users inject data, could lead to easy XSS attacks

This comment has been minimized.

Copy link
@atainter

atainter Jul 18, 2017

👍

}
}

// So that we can do a fetch from the page if rendering client-side.
function getItem (req, res, next) {
const itemData = api.getItem()
console.log('API request for item data')
res.setHeader('content-type', 'application/json')
res.send(itemData)
}

module.exports = { getItemRenderer, getItem }
33 changes: 33 additions & 0 deletions examples/pass-server-data/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const express = require('express')
const next = require('next')
const itemRoute = require('./routes/item')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
const server = express()

// Set up home page as a simple render of the page.
server.get('/', (req, res) => {
console.log('Render home page')
return app.render(req, res, '/', req.query)
})

// We have more complex logic here so we've isolated this code into a route file.
server.get('/item', itemRoute.getItemRenderer(app))

// Serve back-end API to support isomorphic rendering.
server.get('/_data/item', itemRoute.getItem)

// Fall-back on other next.js assets.
server.get('*', (req, res) => {
return handle(req, res)
})

server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})

0 comments on commit f4ee2a0

Please sign in to comment.