Nuxt + Vue + Axios + Enviroment Variables - vue.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.

Related

Vue devServer.proxy in vue.config.js not working

I'm using the following configuration in my vue.config.js located in the root of my repository and it's not working.
module.exports = {
devServer: {
proxy: "http://localhost:3030"
}
}
and this is how I'm trying to call it
return await fetch("/posts", options).then((response) =>
response.json()
).catch(e => console.log("Error fetching posts", e));
however when I change the calling code to the code shown below everything works
return await fetch("http://localhost:3030/posts", options).then((response) =>
response.json()
).catch(e => console.log("Error fetching posts", e));
Edit:
I should have mentioned that I'm using Vite for builds as that was causing some other problems for me with environment variables so it's possible they are causing problems with proxies too.
I looked into this more and it turns out that Vite does have proxy features and so I tried updating my code to use their proxy with still no luck.
// vite.config.js
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
"/posts": "http://localhost:3030/posts"
}
})
vue.config.js is intended for Vue CLI scaffolded projects (and your config would've worked there), not for Vite projects. Vite's configuration is stored in vite.config.js.
Your Vite config value for server.proxy contains an unnecessary /posts suffix:
"/posts": "http://localhost:3030/posts"
^^^^^^
The value should just be the base URL to which the original path is appended:
"/posts": "http://localhost:3030"
Example:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/posts': 'http://localhost:3030'
}
}
})
GitHub demo

Why proxy in vue.config.js 404

I have a small front-end and back-end separated project with development environment and production environment, so I want to set the proxy to call api. vue/cli version is 4.6.5.
file structs:
src
axios
api.js
request.js
components
home
LastBlogs.vue
.env.development
.env.production
package.json
vue.config.js
.env.development:
NODE_ENV = 'development'
VUE_APP_BASE_API = '/dev-api'
VUE_APP_API_ADDRESS= 'http://localhost:8080/blog/'
.env.production:
NODE_ENV = 'production'
# base api
VUE_APP_BASE_API = '/api'
# api publicPath
VUE_APP_API_ADDRESS= 'http://localhost:8080/blog'
vue.config.js:
'use strict'
var path = require('path')
module.exports = {
configureWebpack: {
devtool: 'source-map'
},
assetsDir: 'static',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8001,
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: [process.env.VUE_APP_API_ADDRESS], // api地址
changeOrigin: true,
ws: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '/api',
}
}
}
}
}
axios:
import axios from 'axios'
import qs from 'qs'
// import {app} from '../main.js'
console.log(process.env)
/****** 创建axios实例 ******/
const request = axios.create({
baseURL: process.env.VUE_APP_API_ADDRESS,
timeout:5000
})
// some code of interceptors
export default request;
api.js:
import request from './request.js'
var api = process.env.VUE_APP_BASE_API //'/api'
export function getLastBlogs(){
return request({
url: api+'/blog/lastBlogs',
method: 'get'
})
}
I call api in vue file as this:
<script>
import {getLastBlogs} from '#/axios/blogApi.js'
export default {
name: 'LastBlogs',
data() {
return {
blogs: ["AAAA", "BBBB"]
}
},
created: async function(){
let res = await getLastBlogs();
this.blogs = res.data
}
}
</script>
I got 404 at terminal:
error: xhr.js:160 GET http://localhost:8080/blog/dev-api/blog/lastBlogs 404
and the api of back end is ok:
When I put http://localhost:8080/blog/api/blog/lastBlogs in browser, I get this:
{"code":"0","msg":"操作成功","data":[{"id":1,"blogUser":1,"blogTitle":"test1","blogDescription":"for test","blogContent":"ABABABABAB","blogCreated":"2020-09-20T10:44:01","blogStatus":0},{"id":2,"blogUser":1,"blogTitle":"test2","blogDescription":"for test","blogContent":"BABABABABA","blogCreated":"2020-08-20T10:44:01","blogStatus":0}]}
What should I do? Thanks.
So you are configuring Vue CLI dev-server (running on port 8001) to proxy all requests to /api to http://localhost:8080/blog/api (server) but at the same time configure Axios to use baseURL: process.env.VUE_APP_API_ADDRESS ...which means Axios is sending all requests directly to your server instead of proxy...
Just remove that baseURL: process.env.VUE_APP_API_ADDRESS from Axios config
I also believe your pathRewrite option in proxy config is incorrect.
You dispatch request to /dev-api/blog/lastBlogs
Request goes to Vue dev-server (localhost:8001)
Proxy translates /dev-api into http://localhost:8080/blog/dev-api = http://localhost:8080/blog/dev-api/blog/lastBlogs
pathRewrite is applied to whole path part of the URL - /blog/dev-api/blog/lastBlogs - RegEx ^/dev-api will not match
Try to change pathRewrite into [process.env.VUE_APP_BASE_API]: '/api'

Vue SSR bundling the express server with the vue app so the app can be run from the build dist folder copied to a host server

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"

dynamic sitemap generator in vue js project

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.

Vue Relative Paths with the CLI Service

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";