diff --git a/lib/builder/generator.js b/lib/builder/generator.js index 7a8ef0b45d..d94d751048 100644 --- a/lib/builder/generator.js +++ b/lib/builder/generator.js @@ -10,6 +10,7 @@ const _ = require('lodash') const { resolve, join, dirname, sep } = require('path') const { minify } = require('html-minifier') const Chalk = require('chalk') +const { printWarn } = require('../common/utils') const { isUrl, @@ -141,15 +142,22 @@ module.exports = class Generator { } async afterGenerate() { - const indexPath = join(this.distPath, 'index.html') - if (existsSync(indexPath)) { - // Copy /index.html to /200.html for surge SPA - // https://surge.sh/help/adding-a-200-page-for-client-side-routing - const _200Path = join(this.distPath, '200.html') - if (!existsSync(_200Path)) { - await copy(indexPath, _200Path) - } + let { fallback } = this.options.generate + + // Disable SPA fallback if value isn't true or a string + if (fallback !== true && typeof fallback !== 'string') return + + const fallbackPath = join(this.distPath, fallback) + + // Prevent conflicts + if (existsSync(fallbackPath)) { + printWarn(`SPA fallback was configured, but the configured path (${fallbackPath}) already exists.`) + return } + + // Render and write the SPA template to the fallback path + const { html } = await this.nuxt.renderRoute('/', { spa: true }) + await writeFile(fallbackPath, html, 'utf8') } async initDist() { diff --git a/lib/common/options.js b/lib/common/options.js index e2d06d56cf..3a3a262564 100755 --- a/lib/common/options.js +++ b/lib/common/options.js @@ -138,6 +138,11 @@ Options.from = function (_options) { options.transition.appear = true } + // We assume the SPA fallback path is 404.html (for GitHub Pages, Surge, etc.) + if (options.generate.fallback === true) { + options.generate.fallback = '404.html' + } + return options } @@ -223,6 +228,7 @@ Options.defaults = { concurrency: 500, interval: 0, subFolders: true, + fallback: '200.html', minify: { collapseBooleanAttributes: true, collapseWhitespace: false, diff --git a/test/fallback.generate.test.js b/test/fallback.generate.test.js new file mode 100644 index 0000000000..f041616d69 --- /dev/null +++ b/test/fallback.generate.test.js @@ -0,0 +1,113 @@ +import test from 'ava' +import { resolve } from 'path' +import { existsSync } from 'fs' +import http from 'http' +import serveStatic from 'serve-static' +import finalhandler from 'finalhandler' +import rp from 'request-promise-native' +import { intercept, interceptLog } from './helpers/console' +import { Nuxt, Builder, Generator, Options } from '..' + +const port = 4015 +const url = route => 'http://localhost:' + port + route +const rootDir = resolve(__dirname, 'fixtures/basic') + +let nuxt = null +let server = null +let generator = null + +// Init nuxt.js and create server listening on localhost:4015 +test.serial('Init Nuxt.js', async t => { + let config = require(resolve(rootDir, 'nuxt.config.js')) + config.rootDir = rootDir + config.buildDir = '.nuxt-spa-fallback' + config.dev = false + config.build.stats = false + + const logSpy = await interceptLog(async () => { + nuxt = new Nuxt(config) + const builder = new Builder(nuxt) + generator = new Generator(nuxt, builder) + + await generator.generate() + }) + t.true(logSpy.calledWithMatch('DONE')) + + const serve = serveStatic(resolve(__dirname, 'fixtures/basic/dist')) + server = http.createServer((req, res) => { + serve(req, res, finalhandler(req, res)) + }) + server.listen(port) +}) + +test.serial('default creates /200.html as fallback', async t => { + const html = await rp(url('/200.html')) + t.false(html.includes('