Serving Vue build files using Express JS

·

4 min read

In this blog, I'd be discussing a recent issue I faced in one of the projects I worked which was in MEVN (MongoDB, Express, Vue and Node) stack. The issue I faced was I was not able to serve nested routes other than '/' through Express on page refresh. If I am on the '/about' URL and I hit page refresh, I was getting a 404 error while serving front-end build files through Express. I tried to go through many articles and blogs most of them did not work for me. Then, I stumbled upon a post that mentioned this library called 'connect-history-fallback-api'. I was able to fix the page refresh 404 I was struggling with, thanks to this library. In this post, I'd create a primitive application in Express and Vue with multiple routes and serve the build files using Express. So, let's get started.

Let's create our Vue front-end first with some routes. Inside the project root folder, create two folders named 'client' and 'server'. Create a Vue app inside the 'client' folder using Vue CLI. I am using Vue CLI 4.5 for this. Choose Vue 2 for this project.

vue create .

Install Vue router, this package keeps on getting updated. I am using version 3.2.0 so just to be safe from any dependency-related errors, we'd install this specific version.

npm i vue-router@3.2.0

Let's create a folder 'pages' inside src which would have our views. Create two files named 'About.vue' and 'Home.vue', these would be our routes for testing.

<template>
  <div>
    <h1>Home Page</h1>
  </div>
</template>

<script>
export default {
  name: 'HomePage',
}
</script>

Now, for routes let's create another separate folder named 'routes' following best practices. Inside this folder create an index.js file with the following contents.

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    component: EmptyLayout,
  },
  {
    path: '/dashboard',
    component: DashboardLayout,
  },
];

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
});

export default router;

Add routing to our 'main.js' file.

import Vue from 'vue'
import App from './App.vue'
import router from './routes/index';

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

The next step is to add the placeholder for these routes inside App.vue file. We would add 'router-view' within which all pages would be served. You can also include sections that you want on all pages like Header and Footer.

<template>
  <div id="app">
    <!-- Can add common components here -->
    <router-view />
  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

Now run the app in development mode through 'npm run serve'. You should be able to see the routing enabled. Test it by navigating to the '/about' URL. To simplify things, we have removed redundant CSS, and only the most essential pieces of code have been preserved. Now let's create a config file for Vue to specify some configuration-related settings. Name this 'config.vue.js' and it should be placed on the same level as the src folder.

module.exports = {
    pages: {
      index: {
        entry: 'src/main.js',
        title: 'Service Desk Management',
      },
    },
    publicPath: '/',
    outputDir: 'build',
  };

In this file, we've specified the build directory where static files after compilation would be kept to be served through Express JS. Now create the build by typing 'npm run build' command. With this step, we've done with front-end part of this application. Now, let's proceed with the creation of the back-end skeleton in Express JS.

Create a .env file to read some configuration settings for the back end. For now, just specify the environment to be used as 'production'. Normally, you'd only want to serve build assets created via Vue in production mode. In development mode, you'd probably like to run both the front-end and back-end of the application concurrently on separate ports.


NODE_ENV="production"

Initialize your root folder as an npm project by typing 'npm init'. Let's install relevant packages now using the terminal.

npm i dotenv express connect-history-api-fallback --save

Now we would create a file called 'server.js' which would act as an entry point to our back-end server.

import path from 'path'
import express from 'express'
import dotenv from 'dotenv'
import history from 'connect-history-api-fallback'

dotenv.config()
const app = express()
const __dirname = path.resolve()

// Handle routing from front-end
app.use(history())

if (process.env.NODE_ENV === 'production') {
  app.use(express.static(path.join(__dirname, '/client/dist')))
  app.use((req, res, next) => {
    res.status(200).sendFile(path.join(__dirname+'/client/dist/index.html'));
  });
} else {
  app.get('/', (req, res) => {
    res.send('API is running....')
  })
}

const PORT = process.env.PORT || 5000

app.listen(
  PORT,
  console.log(
    `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`
  )
)

We have included the necessary libraries for our project. We've initialized our express app and configured it to serve static build files in case the environment is production.

We have imported 'history' from the 'connect-history-api-fallback' package. This would help in serving any unknown routes through vue-router.

Now, try opening 5000 port on localhost. You should be available to perform routing using Vue. Like I mentioned at the start of this post, before using this package, when I was trying to refresh the page while being on a different URL, it threw 404 page not found error. But, it was fixed using 'connect-history-api-fallback'.

That is it for now, let me know if you have any doubts or questions.