Recap
We've covered all the basics (and a few more advanced topics too), and we've now got a site that has textpages, articles with tagging, article and category listing pages, a sitemap, an RSS feed, data-powered social media links, and some nice custom macros and filters to help us on our way.
Nav upgrades
Our current global navigation is pretty basic. It just loops through the pages it's given and outputs a link to each page in a list. We can improve this in two simple ways:
- Add an 'active' class for the current page so that when we come to add our CSS, we can make the active page look active.
-
Add the
aria-current="page"
attribute and value to help assistive software understand our active page and context within the site.
Our navItem macro currently looks like this:
{% macro navItem(entry) %}
<li>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endmacro %}
We can set a new variable to check if the current page URL is contained in the URL of the item the menu is outputting by using our custom contains
filter:
{% macro navItem(entry, page) %}
{% set activePage = true | contains(page.url, [entry.url]) %}
<li>
<a
href="{{ entry.url }}"
class="link{% if activePage %} is_active{% endif %}"
{% if activePage %}aria-current="page"{% endif %}
>
{{ entry.title }}
</a>
</li>
{% endmacro %}
To make it work, we've had to add the current page variable into the mix, so now we need to pass that from our nav.njk include too. We just need to update the call to our navItem macro and add the page
variable to it:
{%- for entry in pages %}
{{ navItem(entry, page) }}
{% endfor %}
This will, unfortunately, always set the 'home' link to active because '/' is always going to be part of every URL on the site. On the plus side though, it means we can just remove it from our navigation, because our site name is already a link to the homepage, and that's expected behaviour.
We can just go ahead and remove the eleventyNavigation
Frontmatter metadata from our home page.
Base styles
From an Eleventy point of view, we've got a fairly complete, working static site. But it's got zero style at the moment. There's a million different ways you could probably add style to these pages, but I like to use a basic Gulp task runner set up that processes source CSS files and compiles, transforms and minifies out to a single, sensible production-ready stylesheet.
We're going to need a bunch of NPM packages installed to get this working, so let's start there:
Create a new file called package.json
in the root folder of the site with the following content:
{
"name": "mysexyeleventysite",
"version": "1.0.0",
"description": "",
"main": "Gulpfile.js",
"scripts": {
"prod": "concurrently \"npx gulp --production\" \"npx eleventy\"",
"dev": "concurrently \"npx @11ty/eleventy --serve\" \"npx gulp dev\""
},
"browserslist": [
"last 2 major versions"
],
"devDependencies": {
"@11ty/eleventy": "^2.0.1",
"@11ty/eleventy-navigation": "^0.3.5",
"@11ty/eleventy-plugin-rss": "^1.2.0",
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.7",
"@babel/register": "^7.23.7",
"@babel/runtime-corejs3": "^7.23.7",
"autoprefixer": "^10.4.16",
"concurrently": "^8.2.2",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-postcss": "^9.0.1",
"gulp-rename": "^2.0.0",
"html-minifier": "^4.0.0",
"postcss": "^8.4.33",
"postcss-import": "^16.0.0",
"postcss-lightningcss": "^1.0.0",
"postcss-nested": "^6.0.1",
"postcss-preset-env": "^9.3.0",
"postcss-reporter": "^7.0.5",
"postcss-scss": "^4.0.9",
"postcss-variable-compress": "^3.0.0"
}
}
This file is used to tell NPM what packages we want and at what versions, but it also lets us set up node scripts and set up our browserslist so any clever CSS plugins we use will know what browsers we want to support.
This does nothing on its own though, and if we want to install all these packages, we need to tell NPM to do just that. In the root folder of the site run:
npm i
Now we need to set up our CSS tasks in Gulp. Create a new file in the root of the site called gulpfile.js
and put this code in it:
const gulp = require("gulp");
const postcss = require("gulp-postcss");
const rename = require("gulp-rename");
const lightningCss = require("postcss-lightningcss");
const nested = require("postcss-nested");
const partialImports = require("postcss-import");
const presetEnv = require("postcss-preset-env");
const reporter = require("postcss-reporter");
const variableCompress = require("postcss-variable-compress");
function watchCss(done) {
const cssFiles = 'src/css/**/*.css';
const watcher = gulp.watch(cssFiles);
watcher.on("change", function (path, stats) {
console.log(`Watcher fired for: ${path}`);
postCss();
});
done();
}
function postCss() {
const entryFilename = "src/css/index.css";
const outputFilename = "site.css";
const outputDest = "dist/css";
return gulp
.src(entryFilename, { sourcemaps: true })
.pipe(
postcss([
partialImports,
presetEnv({
features: {
"cascade-layers": false,
},
}),
nested,
lightningCss,
variableCompress,
reporter({
clearReportedMessages: true,
clearAllMessages: true,
throwError: false,
positionless: "last",
}),
]),
)
.pipe(rename(outputFilename))
.pipe(
gulp.dest(outputDest, { sourcemaps: "." })
)
;
}
gulp.task("default", postCss);
gulp.task("dev", gulp.series(postCss, watchCss));
I'll not go through what each and every item in here does, but suffice to say this is enough to give us a basic PostCSS set up which will consume source CSS files and automatically check if any of the modern CSS we've written needs a bit extra help for any browser we're supporting or not. It will concatenate all our partial style files and it will allow us to nest our CSS too.
Setting up Gulp tasks properly and finding all the plugins you might need is a whole other volume of documentation altogether!
There's a 'watch' task as well so that when we're running locally we can keep an eye on our source CSS file and automatically run the compilation step as soon as any new file changes are saved.
Now that we have our package file, our tasks file and we've installed our new packages too, we need to add a few things before we can get our CSS compiling. First of all, create a new folder at /src/css/
and create a file in here called index.css
with a few basic styles in (just so we can see that it works!):
body {
font-size: 16px;
margin: 0;
font-family: system-ui;
}
Now we need to make sure our base templates are going to pull our new CSS file in when it's been compiled. Open up our head include and add the following to it:
<link rel="stylesheet" href="{{ '/css/site.css' | url }}">
<link rel="preload" href="{{ '/css/site.css' | url }}" as="style">
If you now go to your terminal and run the development command: npm run dev
instead of our previous command npx @11ty/eleventy --serve
you should get both your Eleventy site build as well as your new stylesheet compiling. Go to the site and refresh and you should at least see that the browser default serif font has been replaced most likely with a sans serif fontface. Success!
Social sharing metadata
We can give Facebook, Twitter, Pinterest and LinkedIn (at least!) a bit of a helping hand with how our pages will look when shared on different platforms. First we'll need to add the right meta elements into our head include:
<meta property="og:site_name" content="{{ settings.sitename }}">
<meta property="og:title" content="{{ title }}">
<meta property="og:description" content="{{ description }}">
<meta property="og:type" content="website">
<meta property="og:image" content="{{ settings.domain }}/img/social/sharer.jpg">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="800">
<meta property="og:image:alt" content="{{ description }}" />
<meta property="og:url" content="{{ settings.domain }}{{ page.url }}">
<meta property="article:published_time" content="{{ settings.lastupdated }}" />
<meta property="article:author" content="{{ settings.author }}" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@{{ settings.xhandle }}">
<meta name="twitter:creator" content="@{{ settings.xhandle }}">
You might notice in there that we're pulling our domain name, sitename and our X handle from our settings.json global data file. You'll need to get an image and place it at /public/img/social/sharer.jpg
too.
Copyright year
We can also update our footer's copyright message with the current year at build time with a nice little addition to our footer include and our Eleventy config file:
eleventyConfig.addShortcode('year', () => `${new Date().getFullYear()}`);
And in our footer file:
<footer>
© 1982 - {% year %}. Me! Because I made this.
{% include 'social.njk' %}
</footer>
What else?
This is only scraping the surface of what Eleventy can do. There are other plugins to help expand your functionality out there, and you can always roll your own too.
If you're not a superfan of Nunjucks (not sure why that would ever happen, it's lovely!) then there's also plenty of choice of other ways to template for Eleventy too.