I am currently keen to updating my existing Nuxt 2 project to Nuxt Bridge as documented here:
https://v3.nuxtjs.org/getting-started/bridge
As for my nuxt.config file I used module.exports = { //config }
Exchanging it with the:
import { defineNuxtConfig } from '#nuxt/bridge'
export default defineNuxtConfig({
// Your existing configuration
})
leads to a webpack error for me because of the "#nuxtjs/firebase" module:
How can I fix this?
You must specify an alias for tslib:
import { defineNuxtConfig } from '#nuxt/bridge'
export default defineNuxtConfig({
alias: {
tslib: 'tslib/tslib.es6.js'
}
})
Follow this issue: https://github.com/nuxt/bridge/issues/25
I have created a Vue SSR application and all instructions/wikis/blogs I have read only tell you how to run the application in a development environment. They do not tell you how to run the application in a production environment.
I have previously written the same app in React SSR application. In that app the build produces a "dist" folder containing the bundle "server_bundle.js". This bundle contains the Express server (server.js) AND the React code. I can run the application from within the "dist" folder using
node dist/server_bundle.js
In the Vue SSR application, the build also produces a "dist" folder. However it contains a "vue-ssr-bundle.json" file which does NOT include the express server (server.js). To run the application in development I have to use the Express server file located in the root of my project rather than running everything from the "dist" directory
node ./server.js
This is OK in development as I am working within my project but in production this will not work as I have to run everything from the "dist" folder.
Building and Running
The app is built using the following commands (in dev i add the "--watch" argument)
webpack --config webpack.server.config.js
webpack --config webpack.client.config.js
These build scripts create and populate the dist (server) and public (client) folders
project_root
- dist
- vue-ssr-bundle.json
- public
- 0_client_bundle.js
- client_bundle.js
- any static images, such as "myImage.jpg"
To run the application I run the Express server using
node ./server.js"
See that I am using "server.js" in the root of my project. It is NOT in the dist folder.
ISSUE
So how would I run the application in production which does NOT have my project code, it only has the "dist" and "public" folders produced by my builds?
My code
Project structure
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- pages
- components which are top level pages
- server
- server_main.js
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- index.html (the template html file to have content inserted into)
- server.js (the Express Server file)
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
src/client/client_main.js
import { createApp } from '../app'
const { app, router, store } = createApp()
...
router.onReady(() => {
app.$mount('#app')
})
src/server/server_main.js
import { createApp } from '../app'
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject({ code: 404 })
}
resolve(app)
}, reject)
})
}
src/app.js
import Vue from 'vue'
import App from './App.vue'
import { createStore } from './vuex/store'
import { createRouter } from './router'
import { sync } from 'vuex-router-sync'
export function createApp () {
const store = createStore()
const router = createRouter()
sync(store, router)
const app = new Vue({
router,
store,
render: h => h(App)
})
return { app, router, store }
}
server.js
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8')
}
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = { url: req.url }
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found')
} else {
res.status(500).end('Internal Server Error')
}
} else {
res.end(html)
}
})
});
server.listen(8080);
webpack.base.config.js
const webpack = require('webpack')
module.exports = {
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-loader', 'babel-loader''file-loader'
},
resolve: {
alias: {'vue$': 'vue/dist/vue.esm.js'},
extensions: ['*', '.js', '.vue', '.json']
},
performance: {
hints: false
},
}
webpack.client.js
var path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const config = {
entry: './src/client/main.js',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public'),
},
module: {
rules: I wont put them all here to reduce noise, but i am using 'vue-style-loader', 'css-loader'
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map'
}
module.exports = merge(baseConfig, config);
webpack.server.js
const path = require('path')
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.config.js');
const webpackNodeExternals = require('webpack-node-externals');
const VueSSRPlugin = require('vue-ssr-webpack-plugin')
const config = {
target: 'node',
entry: './src/server/main.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, './dist'),
libraryTarget: 'commonjs2'
},
externals: [webpackNodeExternals()],
devtool: '#source-map',
plugins: [
new VueSSRPlugin(),
]
}
module.exports = merge(baseConfig, config);
What I tried
I expected the server build would have the Express server bundled in with the Vue application code just like React and therefore I would just have to run the bundle from within the dist folder. I think this is a much nicer and cleaner solution.
I tried unsuccessfully to change the Vue app to build and run like the React application.
Remove VueSSRPlugin
I removed the VueSSRPlugin reference in webpack.server.config.js and saw that in the "dist" file i now have the bundle and images, like with React. However this bundle still does not have the Express server in it. I still did not know how to get the express server in the dist folder
Move express file into src/server folder
I thought about moving the express file (server.js) into my projects source hoping it will be added to the bundle. However I did not know how to change the express file seeing as it has a reference to the JSON file which the express server file ends up in.
I have resolved this for the time being by copying to my server the following
index.html
server.js
the "dist" folder (containing the server bundle)
the "public" folder (containing the client bundle, stylesheet and other files)
Then on the server i can run
node server.js
as the server.js and index.html has been copied onto the server.
I still prefer the way React is built so that the express server.js file and index.html are in the server bundle, but at least this is working.
I used my React SSR to figure out how to bundle all of my Vue SSR application including the express server into the server bundle.
The downside of this solution is that I can no longer use the "index.html" template and the "bundleRenderer".
The upside of this solution, is that the express server can now make use of variables set in WebPack config.
Project structure
My project structure is now the following. I moved the express server file into the src/server folder and removed the "index.html" file.
project_root
- src
- assets
- any static images, such as "myImage.jpg"
- client
- client_main.js
- components
- lots of files
- server
- server_main.js
- server.js (the Express Server file)
- vuex
- folders and files containing code pertaining to vuex
- app.js
- App.vue
- router.js
- webpack.base.config.js
- webpack.client.config.js
- webpack.server.config.js
WebPack config changes
"webpack.base.config.js" no longer needs
devtool: '#source-map',
"webpack.server.config.js" no longer needs the "VueSSRPlugin" so the following can be removed
const VueSSRPlugin = require('vue-ssr-webpack-plugin');
plugins: [
new VueSSRPlugin(),
],
Change the server entry to be the express server file
entry: './src/server/server.js',
Server main file : src/server/server_main.js
Return the VUEX store as well as the Vue app. This is because we have to manually extract the store data, serialize it and add it to the HTML returned to the client.
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve({ app, store });
}, reject);
});
Create a renderer.js file responsible for generating the HTML to return to the client (i.e. the replacement of the index.html
NOTE: You do not have to have a separate file for this, you could include this code in your express server file if you so desire.
import serialize from 'serialize-javascript';
export default (store, html) => {
return `
<html>
<head>
<link rel="icon" href="/favicon.png" type="image/png">
</head>
<body>
<script>
window.__INITIAL_STATE__ = ${serialize(store.state)}
</script>
<div id="root">${html}</div>
<script src="/client-bundle.js"></script>
</body>
</html>
`;
};
Express server file changes (src/server/server.js) - no longer use bundleRenderer
Change the express server file to use the vue server renderer and not the bundle renderer.
Call "generateApp" (i.e. the function in server_main.js which is unchanged) and when that promise has resolved all the HTML has been generated and the VUEX store contains all the data. Call renderToString on the vue renderer passing in the Vue app that has been created. In the callback function for renderToString call the renderer passing the HTML generated and the VUEX store. The renderer will create the HTML to return including the serialised store. The HTML is then returned to the client.
import generateApp from './server_main.js';
import renderer from './renderer';
const express = require('express');
const vueServerRenderer = require('vue-server-renderer').createRenderer();
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = { url: req.url };
generateApp(context).then((createdAppObj) => {
const { app, store } = createdAppObj;
vueServerRenderer.renderToString(
app,
(err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
// BUILD UP THE HTML TO RETURN
const content = renderer(store, html);
res.send(content);
}
},
);
});
});
server.listen(8080);
package.json change
Now when everything is built the "dist" directory will contain the following
- server-bundle.js
This contains the express server file as the entry file, therefore to run the application you execute
node ./server.js
One advantage of this solution: Express Server can make use of variables set up in WebPack config
Vue supports adding variables into your "index.html" template using Template Interpolation, see https://ssr.vuejs.org/guide/#using-a-page-template.
However in my case I needed to use a variable setup in my WebPack config to put into the "index.html".
My WebPack config sets variables from a ".env" file which is then used in my Vue app.
I need to use a variable in this ".env" file in my "index.html". As the Express Server was not being parsed and bundled by WebPack, it would not have the variables set. I would have to read the ".env" file in the express server file. This would mean i would have to also have the ".env" file on my production environment where the express server file is located in order for it to read from it.
With the change described in this comment, WebPack bundles the express server file now, therefore the express server file has access to all variables set in WebPack, therefore it will have access to variables set from ".env"
i want to generate sitemap based on routes in vue js project but i could not find any solution for this?
i found vue-router-sitemap package in npm but it did not mention any example and i totally confused? what is the solution for this?
anyway is there any way to access routes object in another normal js file ?
this is the xample of that but what is the app! and how can i use these?
// sitemapMiddleware.js
import VueRouterSitemap from 'vue-router-sitemap';
import path from 'path';
import { router } from 'router';
export const sitemapMiddleware = () => {
return (req, res) => {
res.set('Content-Type', 'application/xml');
const staticSitemap = path.resolve('dist/static', 'sitemap.xml');
const filterConfig = {
isValid: false,
rules: [
/\/example-page/,
/\*/,
],
};
new VueRouterSitemap(router).filterPaths(filterConfig).build('http://example.com').save(staticSitemap);
return res.sendFile(staticSitemap);
};
};
app.get('/sitemap.xml', sitemapMiddleware());
This middleware should place in vue.config.js.
Running into a problem which is surely related to Webpack.
Tried to do the most basic of services as a smoke test (start small) in a Vue app created by the CLI.
Versions:
Vue CLI: 3.11.0
vue 2.6.10
#vue/CLI-Service 4.0.5
I created a folder called shared inside the src folder of my project. The HelloWorld.vue file is in the components folder. In that file, I imported a data service which I placed inside shared and attempted to use it in the Created event of the HelloWorld component:
<script>
import { dataService } from "../shared";
export default {
name: "HelloWorld",
props: {
msg: String
},
created() {
dataService.addWebSite();
}
};
</script>
The data service is very simple and just meant to hit an API (data.service.js):
import * as axios from 'axios';
const addWebSite = function () {
axios({
method: 'POST',
url: 'https://localhost:44362/WebSites/CreateWebSite',
data: {
name: "Pluralsight",
url: "http://Pluralsight.com"
}
}).then((response) => {
var discard = response.data;
return discard;
});
};
export const dataService = {
addWebSite: addWebSite
};
When I execute npm run serve, I see the following error message:
ERROR Failed to compile with 1 errors 6:13:39 PM
This relative module was not found:
../shared in ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/HelloWorld.vue?vue&type=script&lang=js&
I'm guessing this is some kind of Webpack relative path thing, but am at a loss and have not been able to solve it using Google.
The vue.config.js looks like this:
module.exports = {
configureWebpack: {
devtool: 'source-map',
}
};
And I have tried adding a publicPath property of both './' and '/' to that exported object.
Anyone know what's going on?
When you try to import from a folder instead of file, like this
import { dataService } from "../shared";
it implies that you actually want to import from "../shared/index.(any_supported_extension)". But since your file is actually named data.service.js you will have to change your import to
import { dataService } from "../shared/data.service.js";
I am unable to provide axios a baseUrl using an enviroment variable
I am using nuxt/vue/axios
What I have is roughly:
// axios.js
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
axios.defaults.baseURL = process.env.BASE_URL
Vue.use(VueAxios, axios)
and
// index.vue (front page as it appears on the client)
populateFilters () {
Vue.axios.get('filters').then((response) => {
this.$store.commit('data/filters', response.data)
})
.catch((e) => {
console.error(e)
})
}
and inside nuxt.config.js
// nuxt.config.js
build: {
extractCSS: true,
extend (config, ctx) {
// Run ESLint on save
const envLoad = require('dotenv').config()
if (envLoad.error){
throw result.error
}
}
},
console.log(process.env.BASE_URL) prints the correnct connection string in CMD, however, in chrome web browser it outputs "undefined" and I get the following error
GET http://localhost:3000/filters 404 (Not Found)
meaning that axios (probably) defaults to http://localhost:3000 whenever the baseUrl for axios has not been set.
What I think the problem is
The server/client pair that is loaded up by nuxt has different contexts or that the server loads up axios before the enviroment variables have been loaded
What I need
I need to be able to either:
create .env files that contains (amongst other things) the BASE_URL
define these enviroment variables somewhere in the code (nuxt.config.js ?)
Solution
Please see the accepted answer
In addition, install nuxtjs/dotenv
npm install #nuxtjs/dotenv
and make your nuxt.config.js look like:
// nuxt.config.js
require('dotenv').config()
module.exports = {
modules: [
'#nuxtjs/dotenv',
'#nuxtjs/axios',
],
axios: {
baseURL: process.env.BASE_URL
},
}
note that require('dotenv').config() must be at the top
Theres a nuxt axios module you can use. You can declare it in the module sectiont of your nuxt.config.js so you dont need your file axios.js. This is described here https://github.com/nuxt-community/axios-module#usage
By loading it in the modules in the nuxt.config.js file you will have your axios instance accesible with this.$axios in your components.
You can declare the base url in the nuxt.config.js
modules: [
// Doc: https://github.com/nuxt-community/axios-module#usage
['#nuxtjs/axios', {
baseURL: 'http://jsonplaceholder.typicode.com'
}]
],
/*
** Axios module configuration
*/
axios: {
// See https://github.com/nuxt-community/axios-module#options
},
For "other environement variables" you can use the vuex store to have them available to all components.