From 33f8f282099cb34db2c405aabb883af836d6dc2a Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sat, 13 Jan 2018 20:29:09 +0530 Subject: [PATCH 1/8] Move security related test cases into a its own file. --- .../integration/production/test/index.test.js | 39 ++-------------- test/integration/production/test/security.js | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+), 35 deletions(-) create mode 100644 test/integration/production/test/security.js diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js index d6fd6eb21e5c9..b5f3f71a30453 100644 --- a/test/integration/production/test/index.test.js +++ b/test/integration/production/test/index.test.js @@ -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', () => { @@ -162,4 +129,6 @@ describe('Production Usage', () => { }) dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q)) + + security(context) }) diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js new file mode 100644 index 0000000000000..1f83c145869bc --- /dev/null +++ b/test/integration/production/test/security.js @@ -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() + }) + }) +} From 77c10b24c5e2d31d86bc4eebbae35563b0f285d7 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sat, 13 Jan 2018 22:36:14 +0530 Subject: [PATCH 2/8] Removes the unused renderScript function --- server/render.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/server/render.js b/server/render.js index bc0ae6fa989ef..b635a005df88a 100644 --- a/server/render.js +++ b/server/render.js @@ -7,7 +7,6 @@ import generateETag from 'etag' import fresh from 'fresh' import requireModule from './require' import getConfig from './config' -import resolvePath from './resolve' import { Router } from '../lib/router' import { loadGetInitialProps } from '../lib/utils' import Head, { defaultHead } from '../lib/head' @@ -125,22 +124,6 @@ async function doRender (req, res, pathname, query, { return '' + renderToStaticMarkup(doc) } -export async function renderScript (req, res, page, opts) { - try { - const dist = getConfig(opts.dir).distDir - const path = join(opts.dir, dist, 'bundles', 'pages', page) - const realPath = await resolvePath(path) - await serveStatic(req, res, realPath) - } catch (err) { - if (err.code === 'ENOENT') { - renderScriptError(req, res, page, err, {}, opts) - return - } - - throw err - } -} - export async function renderScriptError (req, res, page, error, customFields, { dev }) { // Asks CDNs and others to not to cache the errored page res.setHeader('Cache-Control', 'no-store, must-revalidate') From 93990ab3fcae1a7e9cabc16e4153738c54e230b2 Mon Sep 17 00:00:00 2001 From: Martin Alix Date: Mon, 15 Jan 2018 16:13:51 -0500 Subject: [PATCH 3/8] Add a nerv example. (#3573) * Add a nerv example. * Fix for indentation/style * Fix for name --- examples/using-nerv/README.md | 44 ++++++++++++++++++++++++++++++ examples/using-nerv/next.config.js | 16 +++++++++++ examples/using-nerv/package.json | 21 ++++++++++++++ examples/using-nerv/pages/about.js | 5 ++++ examples/using-nerv/pages/index.js | 6 ++++ examples/using-nerv/server.js | 29 ++++++++++++++++++++ 6 files changed, 121 insertions(+) create mode 100644 examples/using-nerv/README.md create mode 100644 examples/using-nerv/next.config.js create mode 100644 examples/using-nerv/package.json create mode 100644 examples/using-nerv/pages/about.js create mode 100644 examples/using-nerv/pages/index.js create mode 100644 examples/using-nerv/server.js diff --git a/examples/using-nerv/README.md b/examples/using-nerv/README.md new file mode 100644 index 0000000000000..0156310fdb352 --- /dev/null +++ b/examples/using-nerv/README.md @@ -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/) diff --git a/examples/using-nerv/next.config.js b/examples/using-nerv/next.config.js new file mode 100644 index 0000000000000..c709c9832d43d --- /dev/null +++ b/examples/using-nerv/next.config.js @@ -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 + } +} diff --git a/examples/using-nerv/package.json b/examples/using-nerv/package.json new file mode 100644 index 0000000000000..15cded8d5ce53 --- /dev/null +++ b/examples/using-nerv/package.json @@ -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" + } +} diff --git a/examples/using-nerv/pages/about.js b/examples/using-nerv/pages/about.js new file mode 100644 index 0000000000000..6c501fbd81e18 --- /dev/null +++ b/examples/using-nerv/pages/about.js @@ -0,0 +1,5 @@ +import React from 'react' + +export default () => ( +
About us
+) diff --git a/examples/using-nerv/pages/index.js b/examples/using-nerv/pages/index.js new file mode 100644 index 0000000000000..3e6362aa91002 --- /dev/null +++ b/examples/using-nerv/pages/index.js @@ -0,0 +1,6 @@ +import React from 'react' +import Link from 'next/link' + +export default () => ( +
Hello World. About
+) diff --git a/examples/using-nerv/server.js b/examples/using-nerv/server.js new file mode 100644 index 0000000000000..e28d7afd94154 --- /dev/null +++ b/examples/using-nerv/server.js @@ -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}`) + }) +}) From 4697cbc485855d919ac6c2aef9a433d44b3699b6 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 5 Feb 2018 19:25:44 +0100 Subject: [PATCH 4/8] Release 5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63097d474ac28..4636afcb646a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "5.0.0-universal-alpha.14", + "version": "5.0.0", "description": "Minimalistic framework for server-rendered React applications", "main": "./dist/server/next.js", "license": "MIT", From 7a1d9c18a39860f5711446595543468bfbdef04e Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Tue, 6 Feb 2018 02:48:27 +0530 Subject: [PATCH 5/8] Add multi-zones docs. (#3688) --- readme.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/readme.md b/readme.md index d74bfe19f5563..60352fcfc7e77 100644 --- a/readme.md +++ b/readme.md @@ -44,6 +44,7 @@ Next.js is a minimalistic framework for server-rendered React applications. - [CDN support with Asset Prefix](#cdn-support-with-asset-prefix) - [Production deployment](#production-deployment) - [Static HTML export](#static-html-export) +- [Multi Zones](#multi-zones) - [Recipes](#recipes) - [FAQ](#faq) - [Contributing](#contributing) @@ -1220,6 +1221,48 @@ So, you could only use `pathname`, `query` and `asPath` fields of the `context` > Basically, you won't be able to render HTML content dynamically as we pre-build HTML files. If you need that, you need run your app with `next start`. +## Multi Zones + +

+ Examples + +

+ +A zone is a single deployment of a Next.js app. Just like that, you can have multiple zones. Then you can merge them as a single app. + +For an example, you can have two zones like this: + +* https://docs.my-app.com for serving `/docs/**` +* https://ui.my-app.com for serving all other pages + +With multi zones support, you can merge both these apps into a single one. Which allows your customers to browse it using a single URL. But you can develop and deploy both apps independently. + +> This is exactly the same concept as microservices, but for frontend apps. + +### How to define a zone + +There are no special zones related APIs. You only need to do following things: + +* Make sure to keep only the pages you need in your app. (For an example, https://ui.my-app.com should not contain pages for `/docs/**`) +* Make sure your app has an [assetPrefix](https://github.com/zeit/next.js#cdn-support-with-asset-prefix). (You can also define the assetPrefix [dynamically](https://github.com/zeit/next.js#dynamic-assetprefix).) + +### How to merge them + +You can merge zones using any HTTP proxy. + +You can use [micro proxy](https://github.com/zeit/micro-proxy) as your local proxy server. It allows you to easily define routing rules like below: + +```json +{ + "rules": [ + {"pathname": "/docs**", "method":["GET", "POST", "OPTIONS"], "dest": "https://docs.my-app.com"}, + {"pathname": "/**", "dest": "https://ui.my-app.com"} + ] +} +``` + +For the production deployment, you can use the [path alias](https://zeit.co/docs/features/path-aliases) feature if you are using [ZEIT now](https://zeit.co/now). Otherwise, you can configure your existing proxy server to route HTML pages using a set of rules as show above. + ## Recipes - [Setting up 301 redirects](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/) From 523b61569bdaabbbbe47bb80102e4be97f18b502 Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Thu, 15 Feb 2018 23:48:21 -0800 Subject: [PATCH 6/8] with emotion example - hoist styles out of the index page component (#3821) --- examples/with-emotion/pages/index.js | 112 +++++++++++++-------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/examples/with-emotion/pages/index.js b/examples/with-emotion/pages/index.js index 01208e33c5f0c..bf03fdb679480 100644 --- a/examples/with-emotion/pages/index.js +++ b/examples/with-emotion/pages/index.js @@ -7,66 +7,66 @@ if (typeof window !== 'undefined') { hydrate(window.__NEXT_DATA__.ids) } -export default () => { - injectGlobal` - html, body { - padding: 3rem 1rem; - margin: 0; - background: papayawhip; - min-height: 100%; - font-family: Helvetica, Arial, sans-serif; - font-size: 24px; - } - ` +injectGlobal` + html, body { + padding: 3rem 1rem; + margin: 0; + background: papayawhip; + min-height: 100%; + font-family: Helvetica, Arial, sans-serif; + font-size: 24px; + } +` - const basicStyles = css` - background-color: white; - color: cornflowerblue; - border: 1px solid lightgreen; - border-right: none; - border-bottom: none; - box-shadow: 5px 5px 0 0 lightgreen, 10px 10px 0 0 lightyellow; - transition: all 0.1s linear; - margin: 3rem 0; - padding: 1rem 0.5rem; - ` - const hoverStyles = css` - &:hover { - color: white; - background-color: lightgray; - border-color: aqua; - box-shadow: -15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue; - } - ` - const bounce = keyframes` - from { - transform: scale(1.01); - } - to { - transform: scale(0.99); - } - ` +const basicStyles = css` + background-color: white; + color: cornflowerblue; + border: 1px solid lightgreen; + border-right: none; + border-bottom: none; + box-shadow: 5px 5px 0 0 lightgreen, 10px 10px 0 0 lightyellow; + transition: all 0.1s linear; + margin: 3rem 0; + padding: 1rem 0.5rem; +` +const hoverStyles = css` + &:hover { + color: white; + background-color: lightgray; + border-color: aqua; + box-shadow: -15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue; + } +` +const bounce = keyframes` + from { + transform: scale(1.01); + } + to { + transform: scale(0.99); + } +` - const Basic = styled.div` - ${basicStyles}; - ` +const Basic = styled.div` + ${basicStyles}; +` - const Combined = styled.div` - ${basicStyles}; - ${hoverStyles}; - & code { - background-color: linen; - } - ` - const Animated = styled.div` - ${basicStyles}; - ${hoverStyles}; - & code { - background-color: linen; - } - animation: ${props => props.animation} 0.2s infinite ease-in-out alternate; - ` +const Combined = styled.div` + ${basicStyles}; + ${hoverStyles}; + & code { + background-color: linen; + } +` +const Animated = styled.div` + ${basicStyles}; + ${hoverStyles}; + & code { + background-color: linen; + } + animation: ${props => props.animation} 0.2s infinite ease-in-out alternate; +` +export default () => { return (
Cool Styles From 9fb8b93ed919fa8631a4a7f38dfda5a308f54d91 Mon Sep 17 00:00:00 2001 From: Maik Marschner Date: Fri, 16 Feb 2018 16:05:57 +0100 Subject: [PATCH 7/8] Use indexOf instead of startsWith (#3758) * Use indexOf instead of startsWith This fixes an IE11 regression, see #3755 * Lint the code --- lib/head.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/head.js b/lib/head.js index 71b374f9d9e31..019cf3c1608dc 100644 --- a/lib/head.js +++ b/lib/head.js @@ -60,7 +60,7 @@ function unique () { const metaCategories = {} return (h) => { - if (h.key && h.key.startsWith('.$')) { + if (h.key && h.key.indexOf('.$') === 0) { if (keys.has(h.key)) return false keys.add(h.key) } From c66663395bb5cf3ac2eee2e17c7d26ba26114bde Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Tue, 6 Mar 2018 08:56:40 +0100 Subject: [PATCH 8/8] Add event-source-polyfill Fixes an issue where the dev server crashes in IE11. --- client/webpack-hot-middleware-client.js | 1 + package.json | 1 + yarn.lock | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/client/webpack-hot-middleware-client.js b/client/webpack-hot-middleware-client.js index 24477fe5da9fb..a28d58c5b35ed 100644 --- a/client/webpack-hot-middleware-client.js +++ b/client/webpack-hot-middleware-client.js @@ -1,3 +1,4 @@ +import 'event-source-polyfill' import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?autoConnect=false' import Router from '../lib/router' diff --git a/package.json b/package.json index f639ecb0cecd4..bbdbbb90386dc 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "cross-spawn": "5.1.0", "del": "3.0.0", "etag": "1.8.1", + "event-source-polyfill": "0.0.12", "find-up": "2.1.0", "fresh": "0.5.2", "friendly-errors-webpack-plugin": "1.6.1", diff --git a/yarn.lock b/yarn.lock index 7864afe1205e7..eec4db93ac07b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2475,6 +2475,10 @@ event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" +event-source-polyfill@0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/event-source-polyfill/-/event-source-polyfill-0.0.12.tgz#e539cd67fdef2760a16aa5262fa98134df52e3af" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"