Running Nuxt in Parallel with Express
How to wire Nuxt and Express together so you get server-side rendering for the frontend and a real API layer on the backend — both in one Node process.
John Ryan Cottam
1 min read
Running Nuxt in Parallel with Express
Nuxt is my go-to when I need server-side rendering for a Vue app. Client-side Vue works fine, but if SEO matters — crawlers need something to index — Nuxt renders each route from the server and hands them a fully-formed page. Problem solved.
But server-side rendering means you’re already running Node. So you might as well use it. That’s where Express comes in: Nuxt owns the view, Express owns the API. Database queries, third-party requests, email — all of it lives behind Express routes. Clean separation, single process.
Setup
Bootstrap the project and choose Express as the server framework:
yarn create nuxt-app nuxt-with-express
When prompted:
- Server-side framework: Express
- Modules: Axios
Then:
cd nuxt-with-express
yarn dev
If http://localhost:3000/ loads, you’re ready.
The key detail: route order
Open server/index.js. You’ll see Nuxt rendering gets mounted at the bottom with app.use(nuxt.render). Express routes must go above this line — otherwise Nuxt intercepts the request first and your API never runs.
const express = require('express')
const { Nuxt, Builder } = require('nuxt')
const app = express()
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
const nuxt = new Nuxt(config)
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
// Express routes go here — before nuxt.render
app.get('/api/movie', async (req, res) => {
const axios = require('axios')
const { data } = await axios.get('http://www.omdbapi.com/', {
params: { t: req.query.title, apikey: process.env.OMDB_API_KEY }
})
res.json(data)
})
// Nuxt handles everything else
app.use(nuxt.render)
app.listen(3000)
}
start()
The Nuxt side
Create pages/index.vue and call your Express endpoint with Axios:
<template>
<div>
<input v-model="title" placeholder="Movie title..." />
<button @click="search">Search</button>
<div v-if="movie">
<h2>{{ movie.Title }} ({{ movie.Year }})</h2>
<p>{{ movie.Plot }}</p>
</div>
</div>
</template>
<script>
export default {
data: () => ({ title: '', movie: null }),
methods: {
async search() {
const { data } = await this.$axios.get('/api/movie', {
params: { title: this.title }
})
this.movie = data
}
}
}
</script>
That’s the whole pattern. Nuxt renders the view, Express serves the data, and both run in one process on one port.
The full working example is on GitHub.