chrisvfritz / prerender-spa-plugin

Prerenders static HTML in a single-page application.
MIT License
7.32k stars 634 forks source link

Configuring for Vue when publicPath is anything but / #344

Open mahatma-andy opened 5 years ago

mahatma-andy commented 5 years ago

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.

26000 commented 4 years ago

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.

Yzzzed commented 4 years ago

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

26000 commented 4 years ago

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

Yzzzed commented 4 years ago

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

hyteraesa commented 4 years ago

@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' }), ]) }`

takatama commented 4 years ago

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:

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']
        ]
      }
    }
  }
}
lizhengnacl commented 3 years ago

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:

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

cnvoa commented 3 years ago

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']
})