Vue Final Project - Part 8 - Deploying the App
Building for Production
Assume that we are deploying this app in the vue-final-project folder inside the doc root directory of your live (or dev) web server.
This means we need to update the vue.config.js file by adding a publicPath property. This is what it should look like:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
publicPath: "/vue-final-project/"
})
You would not need to update the vue.config.js file if you wanted the app to be the main site for your web server (if you were deploying the app directly into your doc root dir).
If you run the project now, you'll notice that vue-final-project is now added the the url.
Now you can build the app, by running npm run build in the terminal, and then copy the contents of the dist folder to a folder named vue-final-project in the doc root directory of your live web server.
To try it out, open your browser and go to localhost/vue-final-project. If your JSON server is up and running the app should work.
Redirecting Requests to the Vue App
If you manually enter a URL into the browser's URL bar, such as localhost/vue-final-project/login, you'll get a 404 error from your Apache server. This is because the server is unaware of the routes that are set up in the Vue app.
To fix this issue, we can add an .htaccess file to the vue-final-project folder that will redirect all requests to our Vue app, which is index.html.
Create a file named .htaccess inside the vue-final-project folder and paste this into it:
# Turn on the mod_rewrite plugin
RewriteEngine on
#If the file being requested exists, then do not redirect:
RewriteCond %{REQUEST_FILENAME} !-f
#If the directory being requested exists, then do not redirect:
RewriteCond %{REQUEST_FILENAME} !-d
#Redirect all requests for this folder (and children)
#to index.php and pass the requested resource in the query string
RewriteRule ^.*$ index.html [L,QSA]
Now the Apache server will redirect requests for any resource within the vue-final-project folder to index.html.
Setting up the app to point to your dev API project
In api.js, comment out the line that initalizes the apiURL variable and then put a line under it that initalizes the variable to point to the API project like so:
//let apiURL = 'http://localhost:8888' // FOR REQUESTS TO THE JSON SERVER
let apiURL = 'http://localhost/api' // FOR REQUESTS TO THE DEV API PROJECT
Updating the login() function
Unfortinately, our login() function in api.js will not work for the API project. The API expects a POST request with the email and password sent in the body of the request. Currently the login() function sends a GET request with the email and password appended to the URL (as URL parameters).
Update the login() function in api.js to look like this:
export function login(email, password) {
if(apiURL == "http://localhost:8888"){
return ax.get(`users/?email=${email}&password=${password}`).then(resp => {
if(resp.data.length == 1){
return resp.data[0]
}else{
return null
}
});
}else{
return ax.post("login/", { email, password }).then(resp => resp?.data);
}
}
We've added an IF statement that determines how to configure the request based on the apiURL variable.
There are better ways of handling the differences in environments, but this solution works for now.
Now you'll have to re-build the Vue app and then copy the contents of the dist folder into the vue-final-project folder on your Apache server. Try it out. Make sure not to delete the .htaccess file from the vue-final-project folder
We need to add a logout() function to api.js, which does a GET request to /localhost/api/logout.
export function logout(){
if(apiURL == "http://localhost:8888"){
return;
}else{
return ax.get("logout/");
}
}
We don't need to send this request when using the JSON server, so we simply return the function if the apiURL is pointing it.
When the API project receive the request, it will delete the user's session on the server.
We need to call this function from the NavBar component when a user logs out. So, in NavBar.vue, import the logout() method from api.js like so:
import {logout as apiLogout} from '../api.js'
Note that we are importing it as apiLogout because we already have a logout() method in this component.
Now make sure to call the apiLogout() method from inside the body of the login() method, put it just before the redirect back to the Login component.
Build the app again, and deploy it to your Apache server, make sure to view the network requests in the browser tools to verify that the logout() function is sending the request to the server.
The problem we have now is that we have to manually set the apiURL variable whenever we swith the environments. Ideally, we'd put logic api.js to automatically detect the environment. Let's try that now.
Instead of manually setting the apiURL in api.js, replace the two lines that initialize apiURL (one of which is currently commented out) with the following:
let apiURL = 'http://localhost:8888' // FOR REQUESTS TO THE JSON SERVER
if(!location.port){
if(location.hostname == "localhost"){
apiURL = 'http://localhost/api' // FOR REQUESTS TO THE DEV API PROJECT
}else{
apiURL = "https://your-domain/api"; // FOR REQUESTS TO THE LIVE API PROJECT
// Leave off the wwww!
// TODO: Ensure that https is being used
}
}
Make sure you replace 'your-domain' with your domain!
The first IF statment checks to see if the Vue app is on a server that is running on a port other than the default. Remember that when you launch your app with npm run serve, npm will start a server on port 8080 (sometimes it uses a different port, if 8080 is already in use). Your Apache server is running on the default port, so to make requests to it, you don't need to include a port number in the URL.
The nested IF statement checks that server name (host name) that the Vue app is running in. If it's 'localhost' then we can assume that it's our dev Apache server. Otherwise, it must be our live server.
Build the app, deploy it to your dev Apache server, and make sure it works. Also, make sure to run it with npm run serve to verify that it works with your JSON server.
Ensuring that HTTPS is used
On the live server, we need to make sure that all requests are using HTTPS. I found a code sample that uses JavaScript to detect a page (such as our Vue app) is running on HTTPS. And if it isn't it will redirect. Replace the comment that says '// TODO: Ensure that https is being used' with this code.
// Redirect to https if it's not being used
if (location.protocol !== 'https:') {
location.replace(`https:${location.href.substring(location.protocol.length)}`);
}
Now, assuming that you have successfully deployed your API project to your live server, you should be able to build the Vue app one more time and copy the contents of the dist folder into a folder named vue-final-project, which is in the doc root dir of your live Apache server.
We may also have to redirect any requests that include 'www.' to the URL that doesn't. This can help to avoid CORs requests.
if (window.location.hostname.indexOf("www") == 0) {
window.location = window.location.href.replace("www.","");
}
OPTIONAL: Configuring Axios to Send the x-id header with each request
As you know from the Adv Topics class, we had ran into issues when we started making CORS requests (the browser will not send cookies when making cross-origin requests).
At the moment, we don't have this problem because the Vue app, and the API project are are considered to be on the same server (so the requests sent by the Vue app are not 'cross-origin'). But, if for some reason, you needed to make CORS requests to the API then you'll need to configure the Axios requests to send the x-id header.
Put this code just after the ax variable is initialized (in api.js).
var sessionId;
// You can 'intercept' all requests made by ax, and add your own headers to the request
// Here we're adding the x-id header so that the server can keep the session going
ax.interceptors.request.use(request => {
if(sessionId){
request.headers['x-id'] = sessionId;
}
return request;
});
// here we extract the x-id header from the response
// and use it to set our sessionId variable
ax.interceptors.response.use(response => {
if(response.headers['x-id']){
sessionId = response.headers['x-id'];
}
return response;
});