Chapter 9: Prepare project for deployment. Environmental variables, production/development. Logger. SEO, robots.txt, sitemap.xml. Compression and security. Deploy project. Heroku. Testing deployed project. AWS Elastic Beanstalk.
The section below is a preview of Builder Book. To read the full text, you will need to purchase the book.
In Chapter 9, you'll start with the codebase in the 9-begin folder of our builderbook repo and end up with the codebase in the 9-end folder. We'll cover the following topics in this chapter:
Prepare project for deployment - Environmental variables, production/development - Logger - SEO: robots.txt, sitemap.xml - Google Analytics - Compression and security - Express route /_next/*
Deploy project - Heroku - Testing deployed project - AWS Elastic Beanstalk
In the previous chapter, Chapter 8, you learned about Stripe API and implemented infrastructure that allows our application to sell content (for example, a book). You also learned abour Mailchimp API so your application adds the email address of a signed-up user and buyer to a corresponding Mailchimp list/audience.
In this last chapter of the book, Chapter 9, we will prepare our application for a production environment and deploy our application to two hosting services: Heroku and AWS Elastic Beanstalk.
Prepare project for deployment link
So far, we've been running our app locally at http://localhost:8000
. If you print the value for process.env.NODE_ENV
and run our application locally - you will see undefined
on your logs (either browser or server). The value of dev
, thus, is true
because of how we defined it:
const dev = process.env.NODE_ENV !== 'production';
To deploy our app, we need to: - set the root URL ROOT_URL
to https://builderbook.org
instead of http://localhost:8000
- set the process.env.NODE_ENV
value to production
(sometimes hosting service sets value to production
by default) - when NODE_ENV
is in production, tell our app to use production-level environmental variables (for example, live API keys for Stripe instead of test API keys).
Environmental variables, production/development link
Throughout our app, we defined dev
with this line of code:
const dev = process.env.NODE_ENV !== 'production';
Find this line of code, for example, in your book/9-begin/server/stripe.js
file.
This code says that dev
has a value of true
when the process.env.NODE_ENV
value is not production
. Open server/server.js
and find this snippet:
const dev = process.env.NODE_ENV !== 'production';
const MONGO_URL = process.env.MONGO_URL_TEST;
const options = {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true,
};
mongoose.connect(
MONGO_URL,
options,
);
const port = process.env.PORT || 8000;
const ROOT_URL = `http://localhost:${port}`;
Add the following three console.log()
statements right after the code snippet above:
console.log(process.env.NODE_ENV);
console.log(dev);
console.log(ROOT_URL);
Start your app with yarn dev
and pay attention to the terminal output:
undefined
true
http://localhost:8000
So dev
is true because process.env.NODE_ENV
is undefined
(we, as developers, did not set it!), and ROOT_URL
is http://localhost:8000
.
Our goal is to set process.env.NODE_ENV
, and once it is set, use it to specify a production-specific ROOT_URL
and other environmental variables, such as API keys.
Open package.json
and find the scripts
block.
Prepend NODE_ENV=production
to the dev
command, so it becomes:
"dev": "NODE_ENV=production nodemon server/server.js --watch server",
Start your app with yarn dev
and now the terminal prints:
production
false
http://localhost:8000
> Could not find a valid build in the '.next' directory! Try building your app with 'next build' before starting the server.
[nodemon] app crashed - waiting for file changes before starting...
Alright, not bad! You successfully set the environment to production
.
Next.js tells us that we need to build our app with NODE_ENV=production
before we run it. In the scripts
of package.json
, modify the build
command like this:
"build": "NODE_ENV=production next build",
Run yarn build
. When complete, start your app with yarn dev
. Now the app runs locally but with NODE_ENV=production
. You'll notice that the ROOT_URL
is still http://localhost:8000
. Obviously, we need to change how we define ROOT_URL
- by writing a conditional construct. Replace this line inside server/server.js
:
const ROOT_URL = http://localhost:${port}
;
with:
const ROOT_URL = dev ? process.env.NEXT_PUBLIC_URL_APP : process.env.NEXT_PUBLIC_PRODUCTION_URL_APP;
Remember to add the following env variables to your .env
file:
NEXT_PUBLIC_URL_APP="http://localhost:8000"
NEXT_PUBLIC_PRODUCTION_URL_APP="https://9-end.builderbook.org"
Use your own value for NEXT_PUBLIC_PRODUCTION_URL_APP
!
Now run yarn build
and yarn dev
. The terminal outputs:
production
false
https://9-end.builderbook.org
Try logging in - you'll see that it will fail. This makes sense because we did not add routes from server/google.js
, https://builderbook.org/auth/google
, and https://builderbook.org/oauth2callback
, to our Google OAuth app on Google Cloud Platform. We only added development-related routes http://localhost:8000/auth/google
and http://localhost:8000/oauth2callback
.
We added Express routes /auth/google
and /oauth2callback
to our server with:
setupGoogle({ server, ROOT_URL });
All you need to do is add production-level URLs in addition to development-level URLs to your Google OAuth web app at your Google Cloud Platform dashboard.
However, we still have a problem. The sendRequest()
method inside lib/api/sendRequest.js
also uses ROOT_URL
, and all API methods in our app use the sendRequest()
method to send a request (GET
or POST
) from browser to server. Open lib/api/sendRequest.js
and find this snippet:
const response = await fetch(
`${ROOT_URL}${path}`,
Object.assign({ method: 'POST', credentials: 'same-origin' }, options, { headers }),
);
We suggest, for the sake of reusability, creating a new getRootUrl()
function that contains conditional logic and outputs the proper ROOT_URL
depending on the value of process.env.NODE_ENV
. Create a new file book/9-begin/lib/api/getRootUrl.js
with the following content:
export default function getRootURL() {
const dev = process.env.NODE_ENV !== 'production';
const ROOT_URL = dev ? process.env.NEXT_PUBLIC_URL_APP : process.env.NEXT_PUBLIC_PRODUCTION_URL_APP;
return ROOT_URL;
}
To use getRootUrl
in lib/api/sendRequest.js
, follow these steps:
- import
getRootUrl
function with:import getRootUrl from './getRootUrl';
- update the snippet that contains
ROOT_URL
like this:const response = await fetch( `${getRootUrl()}${path}`, Object.assign({ method: 'POST', credentials: 'same-origin' }, options, { headers }), );
- remove unnecessary code:
const port = process.env.PORT || 8000; const ROOT_URL = `http://localhost:${port}`;
Go ahead and use getRootUrl
inside server/server.js
as well:
import
getRootUrl
withconst getRootUrl = require('../lib/api/getRootUrl');
update the snippet that contains
ROOT_URL
by replacing:const ROOT_URL = `http://localhost:${port}`;
with:
const ROOT_URL = getRootUrl();
keep the following line of code, since
server.listen()
usesport
:const port = process.env.PORT || 8000;
The other location where we will use getRootUrl
is server/stripe.js
:
- import
getRootUrl
withconst getRootUrl = require('../lib/api/getRootUrl');
- replace this line:
const ROOT_URL = 'http://localhost:8000';
with:const ROOT_URL = getRootUrl();
- remove
const port = process.env.PORT || 8000;
, sinceport
is not used elsewhere in thestripe.js
file
Start your app with yarn dev
and look at the terminal:
production
false
https://builderbook.org
This output proves that getRootUrl()
successfully set the proper value for ROOT_URL
.
Note that when you deploy your app to your own custom domain, you should replace https://9-end.builderbook.org
with your actual domain name. Remember to add NEXT_PUBLIC_URL_APP
and NEXT_PUBLIC_PRODUCTION_URL_APP
(with corresponding values) to your book/9-begin/.env
file.
We just went through adding NODE_ENV=production
to the dev
and build
scripts inside book/9-begin/package.json
and then running our app; however, we recommend removing NODE_ENV=production
in those locations of your package.json
file. Otherwise, you will be using production-level environmental variables on your local machine. We added NODE_ENV=production
to the dev
and build
scripts for educational purpose - to show that values for production-level environmental vairables can indeed be used instead of development (local) variables.
Set your dev
and build
scripts in book/9-begin/package.json
back to:
"dev": "nodemon server/server.js --watch server",
"build": "next build",
Next, we should make sure that our app uses live API keys instead of test ones. Let's test it out for our Github keys. Open server/github.js
. Find the const API_KEY
line of code and add console.log()
right after it:
const API_KEY = dev ? process.env.GITHUB_TEST_SECRETKEY : process.env.GITHUB_LIVE_SECRETKEY;
console.log(API_KEY);
Important note - the Github OAuth app does not support multiple domains (Google OAuth does, see above). Therefore, you should create a second Github OAuth app and set https://builderbook.org
and https://builderbook.org/auth/github/callback
for the domain and callback URL. Reminder - in your first Github OAuth app, you set http://localhost:8000
and http://localhost:8000/auth/github/callback
.
Paste GITHUB_LIVE_SECRETKEY
and GITHUB_LIVE_CLIENTID
to your book/9-begin/.env
file. Start your app with yarn dev
and you will see that the terminal printed the proper value for API_KEY
, which is the value you specified for process.env.GITHUB_LIVE_SECRETKEY
inside .env
.
Before we deploy our app, we need to modify the start
command. We stopped using the yarn next
command to start our app since Chapter 2, where we introduced Express server. Thus, we should update it to start our custom Express/Next server. Update it like this:
"start": "node server/server.js"
The final scripts
section inside package.json
should be:
"scripts": {
"dev": "nodemon server/server.js --watch server",
"build": "next build",
"start": "node server/server.js",
"lint": "eslint components pages lib server",
"test": "jest --coverage"
},
After you are done with testing, remove all the console.log()
statements from server/server.js
and server/github.js
.
You've reached the end of the Chapter 9 preview. To continue reading, you will need to purchase the book.