Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/next-start
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const dir = resolve(argv._[0] || '.')

const srv = new Server({ dir })

if (!existsSync(resolve(dir, '.next', 'BUILD_ID'))) {
if (!existsSync(resolve(dir, '.next', 'build-stats.json'))) {
console.error(`> Could not find a valid build in the '.next' directory! Try building your app with 'next build' before starting the server.`)
process.exit(1)
}
Expand Down
10 changes: 1 addition & 9 deletions lib/router/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global window, location */
/* global window */
import _Router from './router'

const SingletonRouter = {
Expand Down Expand Up @@ -76,11 +76,3 @@ export const createRouter = function (...args) {

// Export the actual Router class, which is usually used inside the server
export const Router = _Router

export function _notifyBuildIdMismatch (nextRoute) {
if (SingletonRouter.onAppUpdated) {
SingletonRouter.onAppUpdated(nextRoute)
} else {
location.href = nextRoute
}
}
12 changes: 1 addition & 11 deletions lib/router/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import evalScript from '../eval-script'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL } from '../utils'
import { _notifyBuildIdMismatch } from './'
import fetch from 'unfetch'

if (typeof window !== 'undefined' && typeof navigator.serviceWorker !== 'undefined') {
Expand Down Expand Up @@ -260,14 +259,6 @@ export default class Router extends EventEmitter {
const jsonPageRes = await this.fetchRoute(route)
const jsonData = await jsonPageRes.json()

if (jsonData.buildIdMismatch) {
_notifyBuildIdMismatch(as)

const error = Error('Abort due to BUILD_ID mismatch')
error.cancelled = true
throw error
}

const newData = {
...loadComponent(jsonData),
jsonPageRes
Expand Down Expand Up @@ -320,8 +311,7 @@ export default class Router extends EventEmitter {
}

doFetchRoute (route) {
const { buildId } = window.__NEXT_DATA__
const url = `/_next/${encodeURIComponent(buildId)}/pages${route}`
const url = `/_next/pages${route}`
return fetch(url, {
method: 'GET',
headers: { 'Accept': 'application/json' }
Expand Down
1 change: 0 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ Here's a list of supported events:
- `routeChangeStart(url)` - Fires when a route starts to change
- `routeChangeComplete(url)` - Fires when a route changed completely
- `routeChangeError(err, url)` - Fires when there's an error when changing routes
- `appUpdated(nextRoute)` - Fires when switching pages and there's a new version of the app

> Here `url` is the URL shown in the browser. If you call `Router.push(url, as)` (or similar), then the value of `url` will be `as`.

Expand Down
22 changes: 15 additions & 7 deletions server/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export default async function build (dir) {
const compiler = await webpack(dir, { buildDir })

try {
await runCompiler(compiler)
await writeBuildId(buildDir)
const webpackStats = await runCompiler(compiler)
await writeBuildStats(buildDir, webpackStats)
} catch (err) {
console.error(`> Failed to build on ${buildDir}`)
throw err
Expand All @@ -30,20 +30,28 @@ function runCompiler (compiler) {
if (err) return reject(err)

const jsonStats = stats.toJson()

if (jsonStats.errors.length > 0) {
const error = new Error(jsonStats.errors[0])
error.errors = jsonStats.errors
error.warnings = jsonStats.warnings
return reject(error)
}

resolve()
resolve(jsonStats)
})
})
}

async function writeBuildId (dir) {
const buildIdPath = join(dir, '.next', 'BUILD_ID')
const buildId = uuid.v4()
await fs.writeFile(buildIdPath, buildId, 'utf8')
async function writeBuildStats (dir, webpackStats) {
const chunkHashMap = {}
webpackStats.chunks
// We are not interested about pages
.filter(({ files }) => !/^bundles/.test(files[0]))
.forEach(({ hash, files }) => {
chunkHashMap[files[0]] = { hash }
})

const buildStatsPath = join(dir, '.next', 'build-stats.json')
await fs.writeFile(buildStatsPath, JSON.stringify(chunkHashMap), 'utf8')
}
15 changes: 12 additions & 3 deletions server/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,25 @@ export class NextScript extends Component {
_documentProps: PropTypes.any
}

getChunkScript (filename) {
const { __NEXT_DATA__ } = this.context._documentProps
let { buildStats } = __NEXT_DATA__
const hash = buildStats ? buildStats[filename].hash : '-'

return (
<script type='text/javascript' src={`/_next/${hash}/${filename}`} />
)
}

render () {
const { staticMarkup, __NEXT_DATA__ } = this.context._documentProps
let { buildId } = __NEXT_DATA__

return <div>
{staticMarkup ? null : <script dangerouslySetInnerHTML={{
__html: `__NEXT_DATA__ = ${htmlescape(__NEXT_DATA__)}; module={};`
}} />}
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/commons.js`} /> }
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/main.js`} /> }
{ staticMarkup ? null : this.getChunkScript('commons.js') }
{ staticMarkup ? null : this.getChunkScript('main.js') }
</div>
}
}
53 changes: 22 additions & 31 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ export default class Server {
this.quiet = quiet
this.router = new Router()
this.hotReloader = dev ? new HotReloader(this.dir, { quiet }) : null
this.renderOpts = { dir: this.dir, dev, staticMarkup, hotReloader: this.hotReloader }
this.http = null
this.config = getConfig(this.dir)
this.buildStats = !dev ? require(join(this.dir, '.next', 'build-stats.json')) : null
this.renderOpts = {
dev,
staticMarkup,
dir: this.dir,
hotReloader: this.hotReloader,
buildStats: this.buildStats
}

this.defineRoutes()
}
Expand Down Expand Up @@ -83,31 +90,20 @@ export default class Server {
await this.serveStatic(req, res, p)
},

'/_next/:buildId/main.js': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}

'/_next/:hash/main.js': async (req, res, params) => {
this.handleBuildHash('main.js', params.hash, res)
const p = join(this.dir, '.next/main.js')
await this.serveStatic(req, res, p)
},

'/_next/:buildId/commons.js': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}

'/_next/:hash/commons.js': async (req, res, params) => {
this.handleBuildHash('commons.js', params.hash, res)
res.setHeader('Cache-Control', 'max-age=365000000, immutable')
const p = join(this.dir, '.next/commons.js')
await this.serveStatic(req, res, p)
},

'/_next/:buildId/pages/:path*': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ buildIdMismatch: true }))
return
}

'/_next/pages/:path*': async (req, res, params) => {
const paths = params.path || ['index']
const pathname = `/${paths.join('/')}`

Expand Down Expand Up @@ -291,16 +287,6 @@ export default class Server {
}
}

handleBuildId (buildId, res) {
if (this.dev) return true
if (buildId !== this.renderOpts.buildId) {
return false
}

res.setHeader('Cache-Control', 'max-age=365000000, immutable')
return true
}

getCompilationError (page) {
if (!this.hotReloader) return

Expand All @@ -311,8 +297,13 @@ export default class Server {
const p = resolveFromList(id, errors.keys())
if (p) return errors.get(p)[0]
}
}

function throwBuildIdMismatchError () {
throw new Error('BUILD_ID Mismatched!')
handleBuildHash (filename, hash, res) {
if (this.dev) return
if (hash !== this.buildStats[filename].hash) {
throw new Error(`Invalid Build File Hash(${hash}) for chunk: ${filename}`)
}

res.setHeader('Cache-Control', 'max-age=365000000, immutable')
}
}
4 changes: 2 additions & 2 deletions server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) {
async function doRender (req, res, pathname, query, {
err,
page,
buildId,
buildStats,
hotReloader,
dir = process.cwd(),
dev = false,
Expand Down Expand Up @@ -93,7 +93,7 @@ async function doRender (req, res, pathname, query, {
props,
pathname,
query,
buildId,
buildStats,
err: (err && dev) ? errorToJSON(err) : null
},
dev,
Expand Down
4 changes: 2 additions & 2 deletions test/integration/ondemand/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ describe('On Demand Entries', () => {
})

it('should compile pages for JSON page requests', async () => {
const pageContent = await renderViaHTTP(context.appPort, '/_next/-/pages/about')
const pageContent = await renderViaHTTP(context.appPort, '/_next/pages/about')
expect(pageContent.includes('About Page')).toBeTruthy()
})

it('should dispose inactive pages', async () => {
await renderViaHTTP(context.appPort, '/_next/-/pages/about')
await renderViaHTTP(context.appPort, '/_next/pages/about')
const aboutPagePath = resolve(__dirname, '../.next/bundles/pages/about.json')
expect(existsSync(aboutPagePath)).toBeTruthy()

Expand Down
4 changes: 2 additions & 2 deletions test/integration/production/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('Production Usage', () => {
describe('JSON pages', () => {
describe('when asked for a normal page', () => {
it('should serve the normal page', async () => {
const url = `http://localhost:${appPort}/_next/${app.renderOpts.buildId}/pages`
const url = `http://localhost:${appPort}/_next/pages`
const res = await fetch(url, { compress: false })
expect(res.headers.get('Content-Encoding')).toBeNull()

Expand All @@ -51,7 +51,7 @@ describe('Production Usage', () => {

describe('when asked for a page with an unknown encoding', () => {
it('should serve the normal page', async () => {
const url = `http://localhost:${appPort}/_next/${app.renderOpts.buildId}/pages`
const url = `http://localhost:${appPort}/_next/pages`
const res = await fetch(url, {
compress: false,
headers: {
Expand Down