1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Example: Passing data from server through API (#2594)

* Add example on how to pass data through js api during SSR

Requested in #1117

* Use content negotiation instead of a separate route

* Codereview feedback

* Move security related test cases into a its own file.

* Removes the unused renderScript function

* Add a nerv example. (#3573)

* Add a nerv example.

* Fix for indentation/style

* Fix for name
This commit is contained in:
Chris 2018-02-03 08:11:47 -08:00 committed by Tim Neutkens
parent d103345aa1
commit 7afc008aa7
15 changed files with 333 additions and 35 deletions

View file

@ -0,0 +1,50 @@
[![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.
The server knows whether or not to use next.js to render the route based on the Accept header, which will be
`application/json` when we fetch client-side.
Take a look at the following files:
* server.js
* routes/item.js
* pages/item.js
* operations/get-item.js

View file

@ -0,0 +1,5 @@
{
"title": "Now",
"subtitle": "Realtime global deployments",
"seller": "Zeit"
}

View file

@ -0,0 +1,12 @@
const fs = require('fs')
// 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 }

View file

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

View file

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

View file

@ -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('/_data/item', {headers: {'Accept': 'application/json'}})
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>
)
}
}

View file

@ -0,0 +1,39 @@
const express = require('express')
const next = require('next')
const api = require('./operations/get-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)
})
// Serve the item webpage with next.js as the renderer
server.get('/item', (req, res) => {
const itemData = api.getItem()
app.render(req, res, '/item', { itemData })
})
// When rendering client-side, we will request the same data from this route
server.get('/_data/item', (req, res) => {
const itemData = api.getItem()
res.json(itemData)
})
// 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')
})
})

View file

@ -0,0 +1,44 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/using-nerv)
# Hello World example
## 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 using-nerv using-nerv-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/using-nerv
cd using-nerv
```
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
This example uses [Nerv](https://nerv.aotu.io/) instead of React. It's a "blazing fast React alternative, compatible with IE8 and React 16". Here we've customized Next.js to use Nerv instead of React.
Here's how we did it:
* Use `next.config.js` to customize our webpack config to support [Nerv](https://nerv.aotu.io/)

View file

@ -0,0 +1,16 @@
module.exports = {
webpack: function (config, { dev }) {
// For the development version, we'll use React.
// Because, it supports react hot loading and so on.
if (dev) {
return config
}
config.resolve.alias = {
react: 'nervjs',
'react-dom': 'nervjs'
}
return config
}
}

View file

@ -0,0 +1,21 @@
{
"name": "using-nerv",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"module-alias": "^2.0.0",
"next": "latest",
"nervjs": "^1.2.4",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"license": "ISC",
"devDependencies": {
"react": "~15.6.1",
"react-dom": "~15.6.1"
}
}

View file

@ -0,0 +1,5 @@
import React from 'react'
export default () => (
<div>About us</div>
)

View file

@ -0,0 +1,6 @@
import React from 'react'
import Link from 'next/link'
export default () => (
<div>Hello World. <Link href='/about'><a>About</a></Link></div>
)

View file

@ -0,0 +1,29 @@
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const moduleAlias = require('module-alias')
// For the development version, we'll use React.
// Because, it support react hot loading and so on.
if (!dev) {
moduleAlias.addAlias('react', 'nervjs')
moduleAlias.addAlias('react-dom', 'nervjs')
}
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -7,13 +7,12 @@ import {
nextBuild,
startApp,
stopApp,
renderViaHTTP,
waitFor
renderViaHTTP
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import fetch from 'node-fetch'
import dynamicImportTests from '../../basic/test/dynamic'
import { readFileSync } from 'fs'
import security from './security'
const appDir = join(__dirname, '../')
let appPort
@ -74,23 +73,6 @@ describe('Production Usage', () => {
})
})
describe('With XSS Attacks', () => {
it('should prevent URI based attaks', async () => {
const browser = await webdriver(appPort, '/\',document.body.innerHTML="HACKED",\'')
// Wait 5 secs to make sure we load all the client side JS code
await waitFor(5000)
const bodyText = await browser
.elementByCss('body').text()
if (/HACKED/.test(bodyText)) {
throw new Error('Vulnerable to XSS attacks')
}
browser.close()
})
})
describe('Misc', () => {
it('should handle already finished responses', async () => {
const res = {
@ -111,21 +93,6 @@ describe('Production Usage', () => {
const data = await renderViaHTTP(appPort, '/static/data/item.txt')
expect(data).toBe('item')
})
it('should only access files inside .next directory', async () => {
const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
const pathsToCheck = [
`/_next/${buildId}/page/../../../info`,
`/_next/${buildId}/page/../../../info.js`,
`/_next/${buildId}/page/../../../info.json`
]
for (const path of pathsToCheck) {
const data = await renderViaHTTP(appPort, path)
expect(data.includes('cool-version')).toBeFalsy()
}
})
})
describe('X-Powered-By header', () => {
@ -148,4 +115,6 @@ describe('Production Usage', () => {
})
dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q))
security(context)
})

View file

@ -0,0 +1,45 @@
/* global describe, it, expect
*/
import { readFileSync } from 'fs'
import { join } from 'path'
import { renderViaHTTP, waitFor } from 'next-test-utils'
import webdriver from 'next-webdriver'
module.exports = (context) => {
describe('With Security Related Issues', () => {
it('should only access files inside .next directory', async () => {
const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8')
const pathsToCheck = [
`/_next/${buildId}/page/../../../info`,
`/_next/${buildId}/page/../../../info.js`,
`/_next/${buildId}/page/../../../info.json`,
`/_next/:buildId/webpack/chunks/../../../info.json`,
`/_next/:buildId/webpack/../../../info.json`,
`/_next/../../../info.json`,
`/static/../../../info.json`
]
for (const path of pathsToCheck) {
const data = await renderViaHTTP(context.appPort, path)
expect(data.includes('cool-version')).toBeFalsy()
}
})
it('should prevent URI based XSS attacks', async () => {
const browser = await webdriver(context.appPort, '/\',document.body.innerHTML="HACKED",\'')
// Wait 5 secs to make sure we load all the client side JS code
await waitFor(5000)
const bodyText = await browser
.elementByCss('body').text()
if (/HACKED/.test(bodyText)) {
throw new Error('Vulnerable to XSS attacks')
}
browser.close()
})
})
}