prerender-spa-plugin icon indicating copy to clipboard operation
prerender-spa-plugin copied to clipboard

Configuring for Vue when publicPath is anything but /

Open mahatma-andy opened this issue 5 years ago • 8 comments

I've installed this plugin to pre-render my Vue project, which uses vue-router (in history mode) and Webpack.

The Vue app is not in the root directory of the server serving it. It is in a subdirectory of that server (specifically, /vue, meaning it is served from /vue/dist).

When publicPath: '/vue/dist', in vue.config.js, the plugin only pre-renders around the parent <router-view /> component. If I change publicPath: '/',, the markup for each route is pre-rendered as expected.

This is what my vue.config.js file looks like:

const path = require('path');
const PrerenderSPAPlugin = require('prerender-spa-plugin');
module.exports = {
    publicPath: '/vue/dist',
    configureWebpack: {
        plugins: [
            new PrerenderSPAPlugin({
                staticDir: path.join(__dirname, '/dist'),
                routes: [
                    '/',
                    '/vps',
                    '/contact',
                    '/web-hosting',
                    '/status',
                    '/domain-names',
                    '/agreements',
                    '/reseller-hosting',
                    '/about',
                    '/managed-wordpress'
                ],
            })
        ],
    },
};

It looks as if the pre-renderer is not passed the publicPath, but I am not confident in saying that. I would like to know whether there is a way to pass the publicPath parameter to the pre-renderer, and if so, what it is. I've looked through the open and closed issues on this project, but the suggestions I found assume that the server root is one within the app's base directory. In my case, it is outside this directory.

mahatma-andy avatar Aug 23 '19 16:08 mahatma-andy

I managed to set it up. Yeah, this plugin serves from the root of staticDir, no matter what publicPath is. So my solution was to make Webpack write files to publicPath inside of ./dist, not to its root.

At the top of my webpack.prod.conf:

const PUBLIC_PATH = process.env.PUBLIC_PATH || "/";
if (!PUBLIC_PATH.startsWith("/")) {
  console.error("Please, use an absolute PUBLIC_PATH");
  process.exit(1);
} else if (!PUBLIC_PATH.endsWith("/")) {
  console.error("PUBLIC_PATH should end in '/'");
  process.exit(1);
}

const filePath = path.resolve(__dirname, "dist", PUBLIC_PATH.substr(1));

In Webpack config:

  output: {
    path: filePath,
    filename: "js/[name].[contenthash:8].js",
    publicPath: PUBLIC_PATH,
    chunkFilename: "js/[name].[contenthash:8].js"
  },

And config for this plugin:

new PrerenderSPAPlugin({
  staticDir: path.join(__dirname, "dist"),
  indexPath: path.resolve(filePath, "index.html"),
  routes: [ "", "about", "help" ].map(x => PUBLIC_PATH + x),

  renderer: new Renderer({
    inject: {},
    headless: true,
    renderAfterDocumentEvent: "render-event"
  })
})

This way Webpack writes your files to ./dist/<publicPath>, and PrerenderSPAPlugin serves from ./dist, using an index file located at ./dist/<publicPath>/index.html. It then visits pages <publicPath>, <publicPath>about, <publicPath>help.

E. g. your public path is /vue/. Then Webpack writes files to ./dist/vue/, and prerender starts a webserver with root in ./dist/, but using the ./dist/vue/index.html file as fallback. It then goes to 127.0.0.1/vue/, 127.0.0.1/vue/about, 127.0.0.1/vue/help and saves them.

26000 avatar Oct 24 '19 06:10 26000

If my publish path is a CDN domain name like 'https://test.com/', how can I solve this problem? Please. I would like put bundled package like js and css on Cloud Storage and use CDN to speed up my website. And the first page I would like to use prerendering. @26000

Yzzzed avatar Dec 16 '19 11:12 Yzzzed

@Yzzzed unfortunately, I don't think it's possible to use a public path with your domain name when prerendering. If you specify an absolute URL as publicPath, your index.html will try to load js/css/etc files from your production server, not your local directory. As they don't yet exist on the server, it'll just fail.

An easy, but hacky solution may be to first upload the files to your server as needed, and then use some other prerender tool on the live instance of your code. Or you could try to specify a relative URL as your publicPath and then replace all the links in prerendered files with absolute ones, but this too is a dirty hack and it would probably be hard to make it work correctly.

26000 avatar Dec 16 '19 11:12 26000

thx. I also think it is a dirty way, haha. Maybe I will try to find another one. @26000

Yzzzed avatar Dec 17 '19 16:12 Yzzzed

@26000 i Want to put it in the nginx secondary directory after packaging Follow your guide, prompt this : "Error: ENOENT: no such file or directory, stat 'd:\testspace\proj-hyt-web-prerender\examples\vue2-webpack-router\dist\cn\index.html'"

webpack.config.js `var path = require('path') var webpack = require('webpack') var HtmlWebpackPlugin = require('html-webpack-plugin') const PrerenderSPAPlugin = require('prerender-spa-plugin') const Renderer = PrerenderSPAPlugin.PuppeteerRenderer const VueLoaderPlugin = require('vue-loader/lib/plugin')

const PUBLIC_PATH = process.env.PUBLIC_PATH || "/"; if (!PUBLIC_PATH.startsWith("/")) { console.error("Please, use an absolute PUBLIC_PATH"); process.exit(1); } else if (!PUBLIC_PATH.endsWith("/")) { console.error("PUBLIC_PATH should end in '/'"); process.exit(1); }

const filePath = path.resolve(__dirname, "dist", PUBLIC_PATH.substr(1));

module.exports = { mode: process.env.NODE_ENV, entry: './src/main.js', output: { path: path.resolve(__dirname, './dist'), publicPath: PUBLIC_PATH, filename: 'js/[name].[contenthash:8].js', chunkFilename: "js/[name].[contenthash:8].js" }, module: { rules: [ { test: /.vue$/, loader: 'vue-loader' }, { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } }, { test: /.css$/, use: [ 'vue-style-loader', 'css-loader' ] } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } }, devServer: { historyApiFallback: true, noInfo: false, }, devtool: '#eval-source-map', plugins: [ new VueLoaderPlugin(), ] } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new HtmlWebpackPlugin({ title: 'PRODUCTION prerender-spa-plugin', template: 'index.html', filename: path.resolve(__dirname, 'dist/index.html'), favicon: 'favicon.ico' }), new PrerenderSPAPlugin({ staticDir: path.join(__dirname, "dist"), indexPath: path.resolve(filePath, "index.html"), routes: [ '/', '/about', '/contact' ].map(x => PUBLIC_PATH + x),

  renderer: new Renderer({
    inject: {
      foo: 'bar'
    },
    headless: true,
    renderAfterDocumentEvent: 'render-event'
  })
})

]) } else { // NODE_ENV === 'development' module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"development"' } }), new HtmlWebpackPlugin({ title: 'DEVELOPMENT prerender-spa-plugin', template: 'index.html', filename: 'index.html', favicon: 'favicon.ico' }), ]) }`

hyteraesa avatar Feb 19 '20 06:02 hyteraesa

If you would like to serve the Vue app for subdirectory /vue (https://example.com/vue), you should specify not only publicPath, but also outputDir, staticDir, and indexPath as follows:

  • outputDir is dist/vue
  • routes includes the subdirectory like ['/vue', '/vue/vps', '/vue/contact', ...]
  • staticDir is dist
  • indexPath is dist/vue/index.html
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  publicPath: '/vue/',
  outputDir: 'dist/vue/',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: path.join(__dirname, 'dist'),
            indexPath: path.join(__dirname, 'dist/vue/index.html'),
            ['/vue', '/vue/vps', '/vue/contact']
        ]
      }
    }
  }
}

takatama avatar Apr 26 '20 03:04 takatama

If you set the publicPath (generally, it's a cdn resource address), should confirm that when prerender process happen, the prerenderer can access the assets by the publicPath.

So, one way to solve this problem is:

  • run build
  • upload to cdn (publicPath)
  • run build again

Seems a little gross, but simple enough, and works for me.

lizhengnacl avatar Nov 18 '20 09:11 lizhengnacl

If you would like to serve the Vue app for subdirectory /vue (https://example.com/vue), you should specify not only publicPath, but also outputDir, staticDir, and indexPath as follows:

  • outputDir is dist/vue
  • routes includes the subdirectory like ['/vue', '/vue/vps', '/vue/contact', ...]
  • staticDir is dist
  • indexPath is dist/vue/index.html
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  publicPath: '/vue/',
  outputDir: 'dist/vue/',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: path.join(__dirname, 'dist'),
            indexPath: path.join(__dirname, 'dist/vue/index.html'),
            ['/vue', '/vue/vps', '/vue/contact']
        ]
      }
    }
  }
}

You can add another line Make it work better

new PrerenderSPAPlugin({
    staticDir: path.join(__dirname, 'dist'),
    outputDir: path.join(__dirname, '/dist/vue/'),
    indexPath: path.join(__dirname, 'dist/vue/index.html'),
    ['/vue', '/vue/vps', '/vue/contact']
})

cnvoa avatar Dec 21 '20 10:12 cnvoa