Eleventy is increasing in popularity because it allows us to create nice, simple websites, but also — because it’s so developer-friendly. We can build large-scale, complex projects with it, too. In this tutorial we’re going to demonstrate that expansive capability by putting a together a powerful and human-friendly environment variable solution.
What are environment variables?
Environment variables are handy variables/configuration values that are defined within the environment that your code finds itself in.
For example, say you have a WordPress site: you’re probably going to want to connect to one database on your live site and a different one for your staging and local sites. We can hard-code these values in wp-config.php
but a good way of keeping the connection details a secret and making it easier to keep your code in source control, such as Git, is defining these away from your code.
Here’s a standard-edition WordPress wp-config.php
snippet with hardcoded values:
<?php define( 'DB_NAME', 'my_cool_db' );
define( 'DB_USER', 'root' );
define( 'DB_PASSWORD', 'root' );
define( 'DB_HOST', 'localhost' );
Using the same example of a wp-config.php
file, we can introduce a tool like phpdotenv and change it to something like this instead, and define the values away from the code:
<?php $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load(); define( 'DB_NAME', $_ENV['DB_NAME'] );
define( 'DB_USER', $_ENV['DB_USER'] );
define( 'DB_PASSWORD', $_ENV['DB_PASSWORD'] );
define( 'DB_HOST', $_ENV['DB_HOST'] );
A way to define these environment variable values is by using a .env
file, which is a text file that is commonly ignored by source control.
We then scoop up those values — which might be unavailable to your code by default, using a tool such as dotenv or phpdotenv. Tools like dotenv are super useful because you could define these variables in an .env
file, a Docker script or deploy script and it’ll just work — which is my favorite type of tool!
The reason we tend to ignore these in source control (via .gitignore
) is because they often contain secret keys or database connection information. Ideally, you want to keep that away from any remote repository, such as GitHub, to keep details as safe as possible.
Getting started
For this tutorial, I’ve made some starter files to save us all a bit of time. It’s a base, bare-bones Eleventy site with all of the boring bits done for us.
Step one of this tutorial is to download the starter files and unzip them wherever you want to work with them. Once the files are unzipped, open up the folder in your terminal and run npm install
. Once you’ve done that, run npm start
. When you open your browser at http://localhost:8080
, it should look like this:
Also, while we’re setting up: create a new, empty file called .env
and add it to the root of your base files folder.
Creating a friendly interface
Environment variables are often really shouty, because we use all caps, which can get irritating. What I prefer to do is create a JavaScript interface that consumes these values and exports them as something human-friendly and namespaced, so you know just by looking at the code that you’re using environment variables.
Let’s take a value like HELLO=hi there
, which might be defined in our .env
file. To access this, we use process.env.HELLO
, which after a few calls, gets a bit tiresome. What if that value is not defined, either? It’s handy to provide a fallback for these scenarios. Using a JavaScript setup, we can do this sort of thing:
require('dotenv').config(); module.exports = { hello: process.env.HELLO || 'Hello not set, but hi, anyway 👋'
};
What we are doing here is looking for that environment variable and setting a default value, if needed, using the OR operator (||
) to return a value if it’s not defined. Then, in our templates, we can do {{ env.hello }}
.
Now that we know how this technique works, let’s make it happen. In our starter files folder, there is a directory called src/_data
with an empty env.js
file in it. Open it up and add the following code to it:
require('dotenv').config(); module.exports = { otherSiteUrl: process.env.OTHER_SITE_URL || 'https://eleventy-env-vars-private.netlify.app', hello: process.env.HELLO || 'Hello not set, but hi, anyway 👋' };
Because our data file is called env.js
, we can access it in our templates with the env
prefix. If we wanted our environment variables to be prefixed with environment
, we would change the name of our data file to environment.js
. You can read more on the Eleventy documentation.
We’ve got our hello
value here and also an otherSiteUrl
value which we use to allow people to see the different versions of our site, based on their environment variable configs. This setup uses Eleventy JavaScript Data Files which allow us to run JavaScript and return the output as static data. They even support asynchronous code! These JavaScript Data Files are probably my favorite Eleventy feature.
Now that we have this JavaScript interface set up, let’s head over to our content and implement some variables. Open up src/index.md
and at the bottom of the file, add the following:
Here’s an example: The environment variable, HELLO is currently: “{{ env.hello }}”. This is called with {% raw %}{{ env.hello }}{% endraw %}.
Pretty cool, right? We can use these variables right in our content with Eleventy! Now, when you define or change the value of HELLO
in your .env
file and restart the npm start
task, you’ll see the content update.
Your site should look like this now:
You might be wondering what the heck {% raw %}
is. It’s a Nunjucks tag that allows you to define areas that it should ignore. Without it, Nunjucks would try to evaluate the example {{ env.hello }}
part.
Modifying image base paths
That first example we did was cool, but let’s really start exploring how this approach can be useful. Often, you will want your production images to be fronted-up with some sort of CDN, but you’ll probably also want your images running locally when you are developing your site. What this means is that to help with performance and varied image format support, we often use a CDN to serve up our images for us and these CDNs will often serve images directly from your site, such as from your /images
folder. This is exactly what I do on Piccalilli with ImgIX, but these CDNs don’t have access to the local version of the site. So, being able to switch between CDN and local images is handy.
The solution to this problem is almost trivial with environment variables — especially with Eleventy and dotenv, because if the environment variables are not defined at the point of usage, no errors are thrown.
Open up src/_data/env.js
and add the following properties to the object:
imageBase: process.env.IMAGE_BASE || '/images/',
imageProps: process.env.IMAGE_PROPS,
We’re using a default value for imageBase
of /images/
so that if IMAGE_BASE
is not defined, our local images can be found. We don’t do the same for imageProps
because they can be empty unless we need them.
Open up _includes/base.njk
and, after the <h1>{{ title }}</h1>
bit, add the following:
<img src="https://assets.codepen.io/174183/mountains.jpg?width=1275&height=805&format=auto&quality=70" alt="Some lush mountains at sunset" />
By default, this will load /images/mountains.jpg
. Cool! Now, open up the .env
file and add the following to it:
IMAGE_BASE=https://assets.codepen.io/174183/
IMAGE_PROPS=?width=1275&height=805&format=auto&quality=70
If you stop Eleventy (Ctrl
+C
in terminal) and then run npm start
again, then view source in your browser, the rendered image should look like this:
<img src="https://assets.codepen.io/174183/mountains.jpg?width=1275&height=805&format=auto&quality=70" alt="Some lush mountains at sunset" />
This means we can leverage the CodePen asset optimizations only when we need them.
Powering private and premium content with Eleventy
We can also use environment variables to conditionally render content, based on a mode, such as private mode. This is an important capability for me, personally, because I have an Eleventy Course, and CSS book, both powered by Eleventy that only show premium content to those who have paid for it. There’s all-sorts of tech magic happening behind the scenes with Service Workers and APIs, but core to it all is that content can be conditionally rendered based on env.mode
in our JavaScript interface.
Let’s add that to our example now. Open up src/_data/env.js
and add the following to the object:
mode: process.env.MODE || 'public'
This setup means that by default, the mode
is public. Now, open up src/index.md
and add the following to the bottom of the file:
{% if env.mode === 'private' %} ## This is secret content that only shows if we’re in private mode. This is called with {% raw %}`{{ env.mode }}`{% endraw %}. This is great for doing special private builds of the site for people that pay for content, for example. {% endif %}
If you refresh your local version, you won’t be able to see that content that we just added. This is working perfectly for us — especially because we want to protect it. So now, let’s show it, using environment variables. Open up .env
and add the following to it:
MODE=private
Now, restart Eleventy and reload the site. You should now see something like this:
You can run this conditional rendering within the template too. For example, you could make all of the page content private and render a paywall instead. An example of that is if you go to my course without a license, you will be presented with a call to action to buy it:
Fun mode
This has hopefully been really useful content for you so far, so let’s expand on what we’ve learned and have some fun with it!
I want to finish by making a “fun mode” which completely alters the design to something more… fun. Open up src/_includes/base.njk
, and just before the closing </head>
tag, add the following:
{% if env.funMode %} <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lobster&display=swap" /> <style> body { font-family: 'Comic Sans MS', cursive; background: #fc427b; color: #391129; } h1, .fun { font-family: 'Lobster'; } .fun { font-size: 2rem; max-width: 40rem; margin: 0 auto 3rem auto; background: #feb7cd; border: 2px dotted #fea47f; padding: 2rem; text-align: center; } </style>
{% endif %}
This snippet is looking to see if our funMode
environment variable is true
and if it is, it’s adding some “fun” CSS.
Still in base.njk
, just before the opening <article>
tag, add the following code:
{% if env.funMode %} <div class="fun"> <p>🎉 <strong>Fun mode enabled!</strong> 🎉</p> </div>
{% endif %}
This code is using the same logic and rendering a fun banner if funMode
is true
. Let’s create our environment variable interface for that now. Open up src/_data/env.js
and add the following to the exported object:
funMode: process.env.FUN_MODE
If funMode
is not defined, it will act as false
, because undefined
is a falsy value.
Next, open up your .env
file and add the following to it:
FUN_MODE=true
Now, restart the Eleventy task and reload your browser. It should look like this:
Pretty loud, huh?! Even though this design looks pretty awful (read: rad), I hope it demonstrates how much you can change with this environment setup.
Wrapping up
We’ve created three versions of the same site, running the same code to see all the differences:
All of these sites are powered by identical code with the only difference between each site being some environment variables which, for this example, I have defined in my Netlify dashboard.
I hope that this technique will open up all sorts of possibilities for you, using the best static site generator, Eleventy!
The post Give your Eleventy Site Superpowers with Environment Variables appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.