0

I have been closely trying to follow this Vue SSR guide and in that example the author has placed his Express routes within a file called server.js. Simply for organisation purposes, I wish to keep my Express routes in a router.express.js file within my src/router folder instead of in the root file of server.js.

So this is a cutdown version of my router.express.js file:

const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config
const express = require('express');

const app = express();
const router = express.Router();

const createRenderer = (serverBundle) =>
    vueServerRenderer.createBundleRenderer(serverBundle, {
        runInNewContext: false,
        template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
    });

let renderer;

if (process.env.NODE_ENV === 'development') {
    setupDevServer(app, (serverBundle) => {
        renderer = createRenderer(serverBundle);
    });

} else {
    renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
}

router.get('/', async function (req, res) {
    const context = {
        url: req.params['0'] || '/'
    };
    let html;
    try {
        html = await renderer.renderToString(context);
    } catch (error) {
        if (error.code === 404) {
            return res.status(404).send('404 | Page Not Found');
        }
        return res.status(500).send('500 | Internal Server Error');
    }
        res.end(html);
});

module.exports = router;

The problem is that I also need the vue-server-renderer code to be in the server.js file. I would then make the app require the router.express.js file so the Express routes work like this:

const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config    
const app = express();

     const createRenderer = (serverBundle) =>
            vueServerRenderer.createBundleRenderer(serverBundle, {
                runInNewContext: false,
                template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
            });

        let renderer;

        if (process.env.NODE_ENV === 'development') {
            setupDevServer(app, (serverBundle) => {
                renderer = createRenderer(serverBundle);
            });

        } else {
            renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
        }

    app.use(require('./router/express.router.js'));

Whenever I do this, I get a Webpack error stating that

WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema. - configuration.entry'app' should be a string.

If I remove the vue-server-renderer code from server.js then it runs fine. But the reason for having that code within server.js is so that the development environment works properly. Basically if the code is not in server.js then I cannot use hot-reloading or anything.

If I get rid of router.express.js and put all that code in server.js including the routes, then it all works perfectly including my development environment.

Why can I not (or rather how can I) keep my Express routes in a separate file and still have the vue-server-renderer stuff work?

Update: setup-dev-server.js file:

const setupDevServer = (app, onServerBundleReady) => {
    const webpack = require('webpack');
    const MFS = require('memory-fs');
    const path = require('path');
    const clientConfig = require('./webpack.client.config');
    const serverConfig = require('./webpack.ssr.config');

    // additional client entry for hot reload
    clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app];

    const clientCompiler = webpack(clientConfig);

    // setup dev middleware
    app.use(require('webpack-dev-middleware')(clientCompiler, {
       publicPath: clientConfig.output.publicPath,
       serverSideRender: true,
       logLevel: 'silent',
    }));

    // setup hot middleware
    app.use(require('webpack-hot-middleware')(clientCompiler));

    // watch src files and rebuild SSR bundle
    global.console.log('Building SSR bundle...');
    const serverCompiler = webpack(serverConfig);
    const mfs = new MFS();

    serverCompiler.outputFileSystem = mfs;
    serverCompiler.watch({}, (error, stats) => {
        if (error) throw error;

        global.console.log(
            `${stats.toString({
                colors: true,
                modules: false,
                children: false,
                chunks: false,
                chunkModules: false,
            })}\n\n`
        );

        if (stats.hasErrors()) {
            console.error(stats.compilation.errors);
            throw new Error(stats.compilation.errors);
        }
        // read bundle generated by vue-ssr-webpack-plugin        
        const bundle = JSON.parse(
            mfs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-server-bundle.json'), 'utf-8')
        );
        onServerBundleReady(bundle);
    });
};

module.exports = setupDevServer;
volume one
  • 6,800
  • 13
  • 67
  • 146

1 Answers1

1

1) Inside of your 'router.express.js' file write: module.exports = router; at the bottom of the file.

2) Inside of your 'server.js' file write: const router = require('./router/express.router.js'); at the top of the file.

3) And now where you used to have app.use(require('./router/express.router.js')); replace that with app.use(router);

4) At the top of 'server.js' write const vueSsrBundle = require('../../dist/vue-ssr-server-bundle.json')

5) Lastly replace renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json') With renderer = createRenderer(vueSsrBundle)

EDIT the issue you are facing is on this line And probably related to the file 'app.js' or 'client-entry-js'

stephano
  • 162
  • 2
  • 11
  • Can you the code from the file called 'setup-dev-server'. If there is a value in that file's JSON object called 'app' check if it is in quotes. Check my answer point 5) for update. – stephano Apr 10 '20 at 21:28
  • I just updated my question to include the contents of `setup-dev-server`. I can't see a JSON object with app I don't think? The `webpack.client.config` shows entry as app like this `entry: { app: utils.resolve('/src/entry-client.js') },` – volume one Apr 10 '20 at 21:34
  • Your problem is here: https://github.com/olegpisklov/vue-ssr-simple-setup/blob/master/config/webpack.client.config.js#L13 – stephano Apr 10 '20 at 21:39
  • By a process of deduction the problem is that I cannot have the `setupDevServer(app, (serverBundle) => { renderer = createRenderer(serverBundle); }); } else { renderer = createRenderer(require('../dist/vue-ssr-server-bundle.json')); }` code in both `server.js` and `router.express.js` - it has to be in `server.js` only. So how then will the `html = await renderer.renderToString(context);` in my `router.express.js` routes ever work? Or rather how could I make them work by using `server.js`'s `renderer` instead of its own? – volume one Apr 11 '20 at 10:26