Create A Static Site Using 11ty & Deploy to Neocities

Kia ora, I'm hoping that this guide is going to provide a great starting point for those of you who have been happily constructing your homepages and hosting them on Neocities but want to take it to the next level.

This guide aims to help you create a homepage using the static site generator (SSG) 11ty, commit the code to Github and using a Github action, deploy it to Neocities.

The homepage that we are creating will take advantage of the Nunjucks templating language, allowing us to create a shared header, navigation and footer across all the pages on our homepage.

We will be creating an about, links, contact pages before diving in and creating the ability to add a blog and a list of all blog posts on the blog page!

We will structure and style the page with a standard HTML5 boilerplate and some basic CSS styles that should allow you to add in your unique flavour that we all know that you love to do.

NOTE: This guide assumes the following:

  • You have a basic understanding of HTML and CSS
  • You have a basic understanding of the command line and terminal
  • You have Node.js installed
  • You're using Visual Studio Code (VSCode) as your editor
  • You have a Neocities account
  • You have a Github account

Create a new project

First off, from a terminal, confirm that you have Node and NPM instlled:

node -v && npm -v
v17.4.0
8.3.0

Create a new directory and cd into it:

mkdir 11ty-neocities && cd 11ty-neocities

Initiate a new project:

npm init -y

Install 11ty:

npm install @11ty/eleventy

Once the 11ty installation is complete, open the project in your favourite code editor:

code .

INFO: Typing `code .` into a project directory will open up the project directory in Visual Studio Code.

You should now be in VSCode with the following project structure:

A view of what your VSCode project should look like

Open packages.json and update the scriptions section to the following:

  "scripts": {
"start": "npx @11ty/eleventy --serve",
"build": "npx @11ty/eleventy"
},

The packages.json file should look like this:

{
"name": "11ty-neocities",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npx @11ty/eleventy --serve",
"build": "npx @11ty/eleventy"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@11ty/eleventy": "^1.0.0"
}
}

INFO: This enables us to run npm start to have 11ty run our homepage with a hot-reload, which is provided by Browsersync that comes bundled as part of 11ty's --serve directive.

This is super handy when working on your homepage locally so that everytime you make a change in VSCode, the browser running the local instance of your homepage will auto reload with your most recent changes, amazing!

Create an 11ty config file

From the terminal (or VSCode), create a new file .eleventy.js at the project root:

touch .eleventy.js

Open the file in VSCode and add the following and save:

module.exports = function (eleventyConfig) {
return {
dir: {
input: "src",
output: "public",
includes: "_includes",
},
};
};

INFO: This configuration file tells 11ty what to do.

Setting the input directory to src tells 11ty where to look for changes, this is our working directory.

When changes are detected, 11ty builds the site and outputs it to the output directory public which is where the static html/css/img files are served from, amazing!

.gitignore

As we're going to be commiting our homepage code to Github, create a .gitignore file in the project root:

touch .gitignore

Open the file in VSCode and add the following and save:

# dependencies installed by npm

node_modules

# build artefacts

public

INFO: The .gitignore file is a text file that tells Git which files or folders to ignore in a project.

In this case, our .gitignore file tells git to ignore the node_modules directory and the public directory where our static files are built locally.

Start building the homepage

Now comes the fun part, building our homepage. 11ty has a number of templating languages available to it, with markdown being the most popular. For this guide we're going to use plain old <html> as that will be most familiar to you.

CHALLENGE: If you've heard about Markdown, and want to give it a go, start having a read over on the 11ty documentation pages.

The great thing about Markdown is that you can just write, with it's simple formatting without <html> tags getting in the way of your flow.

Create a src directory at the project root and cd into it:

mkdir src && cd src

Create an index.html file in the terminal or VSCode:

touch index.html

Open the file and add some content:

<html>
<head>
<title>My New 11ty Homepage on Neocities!</title>
</head>
<body>
<h1>Hello World</h1>

<p>
Check out your cool new static site built with
<a href="https://11ty.dev">11ty</a> on
<a href="https://neocities.org/">Neocities</a>.
</p>
</body>
</html>

Now from the terminal start 11ty:

npm start

If everything has been configured right so far you should see the following:

> 11ty-neocities@1.0.0 start
> npx @11ty/eleventy --serve

[11ty] Writing public/index.html from ./src/index.html (liquid)
[11ty] Wrote 1 file in 0.03 seconds (v1.0.0)
[11ty] Watching…
[Browsersync] Access URLs:
-----------------------------------
Local: http://localhost:8080
External: http://192.168.1.119:8080
-----------------------------------
[Browsersync] Serving files from: public

Now you can open up http://localhost:8080 and check out your new 11ty homepage! It should look like this:

A view of what your index file should look like

Amazing! But what we want to avoid is having to write out the <html> and <head> and <body> tags on each and every page, and be able to include a site header, navigation and footer so we don't have to copy and paste the changes across every page each time we update.

Let's checkout templating a layout!

Create a base layout

Create a new directory _includes/ in the src/ directory and cd into it:

mkdir _includes && cd _includes

INFO: In a non-demo situation I would create a layouts/ directory under _includes/ for better organisation. For the sake of simplicity we'll just keep everything in _includes/ for now.

Create a file base.njk in the terminal or VSCode:

touch base.njk

Open the file and add the following:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title }}</title>
</head>
<body>
<header>
<h1>{{ title }}</h1>
</header>
<main>{{ content | safe }}</main>
</body>
</html>

INFO: We've created base.njk as a Nunjucks tempalte file, hence the .njk file extension. This means we can use Nunjucks' double curly braces for using frontmatter variables.

In our layout template we're calling {{ title }} and {{ content }}.

Now, head back to the index.html file you created earlier, delete the contents and add some front matter and some content:

---
title: Hello World!
layout: base.njk
---

<p>
Check out your cool new static site built with
<a href="https://11ty.dev">11ty</a> on
<a href="https://neocities.org/">Neocities</a>.
</p>

<p>This homepage template is perfect for:</p>

<ul>
<li>Creating your own space on the web</li>
<li>Expressing yourself</li>
<li>Displaying all the gifs you've collected</li>
</ul>

<h2>Why do you want a homepage?</h2>
<p>The web was made for personal homepages, make this one yours</p>

If you've kept 11ty running and the broswer running it should look like this:

A view of what your index file should look like now that you're using a tempalte

Amazing! Now lets create the additional pages for our homepage.

Create the following pages in the src/ directory with the terminal or VSCode:

touch about.html && touch links.html && touch contact.html

Open each of them up and add in some front matter and content:

about.html:

---
title: About Me
layout: base.njk
---

<p>Heya 👋 this is my homepage.</p>

links.html:

---
title: Links
layout: base.njk
---

<p>These are some of my favourite websites 🔗</p>
<ul>
<li><a href="https://flamedfury.com">fLaMEdFury.com</a></li>
<li><a href="https://11ty.dev">11ty</a></li>
<li><a href="https://neocities.org">Neocities</a></li>
<li><a href="https://yesterweb.org/">The Yesterweb</a></li>
</ul>

contact.html:

---
title: Contact Me
layout: base.njk
---

<p>Heya 👋 this is my contact page</p>

You should now be able to browse each of these pages if you kept 11ty running on the following urls:

http://localhost:8080/about/ http://localhost:8080/links/ http://localhost:8080/contact/

Great stuff, but that's no use without a navigation! Let's take a look at partials and create a shared header,navigation, and footer to bring our homepage together.

Creating our partials

INFO: In a non-demo situation I would create a partials/ directory under _includes/ for better organisation. For the sake of simplicity we'll just keep everything in _includes/ for now.

In the terminal cd into _includes/ and create two partial files:

cd _includes && touch header.njk && touch navigation.njk && touch footer.njk

Open each of them up and add some front matter and content:

header.njk:

<h1>Welcome to my Homepage</h1>

navigation.njk

<a href="/">Home</a>
<a href="/about/">About</a>
<a href="/links/">Links</a>
<a href="/blog/">Blog</a>
<a href="/contact/">Contact</a>

INFO: It's important to stucture your links with the slashes / on either side of the href /about/ to ensure the links are always from the root of the site.

footer.njk:

<p>This is my footer | © 2022 Me.</p>

Once our paritals are created, open base.njk again and update to include our new elements and partials:

base.njk:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>{{ title }}</title>
</head>

<body>
<header>{% include 'header.njk' %}</header>

<nav>{% include 'navigation.njk' %}</nav>

<main>
<h1>{{ title }}</h1>
{{ content | safe }}
</main>

<footer>{% include 'footer.njk' %}</footer>
</body>

If you've kept 11ty running and the broswer running it should look like this:

A view of what your index file should look like now that you've added your partial tempalte files

Amazing! Now lets add the blog.

Creating the blog

Create a new directory blog in the src directory and cd into it:

mkdir blog && cd blog

Create the following pages in the src/blog directory with the terminal or VSCode:

touch my-first-post.html && touch my-second-post.html && touch my-third-post.html && blog.json

Awesome, Open each of them up in VSCode and add the following:

my-first-post.html:

---
title: My First Blog Post
---

<p>This is my first blog post</p>

my-second-post.html:

---
title: My Second Blog Post
---

<p>This is my second blog post</p>

my-third-post.html

---
title: My Third Blog Post
---

<p>This is my third and final blog post</p>

blog.json

{
"layout": "blog"
}

INFO: What we've done here with the directory file `blog.json` is made it so that every `blog post` in the `/blog/` directory has the `blog.njk` layout applied without having to include in the post front matter.

We better create a blog layout so it renders!

Head back to the _includes directory to create a new layout file:

cd ../_includes && touch blog.njk

Open blog.njk up in VSCode and add the following:

blog.njk:

---
layout: base.njk
---

<article>{{ content | safe}}</article>

INFO: What we've done here is called layout chaining. This is an 11ty feature that lets us extend a child layout from our base layout. That way if we make changes to our `base.njk` layout file, the child layouts like `blog.njk` get the same changes.

Check that your blog posts are loading:

Amazing right? But to make it a blog, we need a blog page that lists all of our blog posts in chronological order. We can do this with a tags collection:

Open blogs.json again and add a key called tags with a value of blog:

blog.json:

{
"layout": "blog",
"tags": "blog"
}

Now 11ty has created a collection called posts and all we have to do ist list them.

Head back to the src/ directory create a blog.html file:

cd .. && touch blog.html

Open it and add the following:

blog.html:

---
title: This Is My Blog
layout: base.njk
---

These are all of my amazing blog posts, enjoy!
<ul>
{% for post in collections.blog | reverse %}
<li>
<a href="">{{ post.data.title }}</a>
</li>
{% endfor %}
</ul>

If you've kept 11ty running and the broswer running it should look like this:

A view of what your blog page should look like

Amazing huh?

Add some styles

Great, so far we have a fully functional home page, but it doesn't look quite right. We need a style sheet.

Create a new css directory in src cd into it and create styles.css:

mkdir css && cd css && touch styles.css

Open styles.css in VSCode and add the following:

styles.css:

/* Global variables. */
:root {
/* Set sans-serif & mono fonts */
--sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
"Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica,
"Helvetica Neue", sans-serif;
--mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace;

/* Default (light) theme */
--bg: #fff;
--accent-bg: #f5f7ff;
--text: #333;
--text-light: #585858;
--border: #d8dae1;
--accent: #0d47a1;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: #212121;
--accent-bg: #2b2b2b;
--text: #dcdcdc;
--text-light: #ababab;
--border: #666;
--accent: #ffb300;
}
}

* {
box-sizing: border-box;
}

html {
/* Set the font globally */
font-family: var(--sans-font);
scroll-behavior: smooth;
}

/* Make the body a nice central block */
body {
color: var(--text);
background: var(--bg);
font-size: 1.15rem;
line-height: 1.5;
margin: 0 auto;
max-width: 40em;
padding: 0 1em;
}

body > header {
text-align: center;
padding: 0 0.5rem 2rem 0.5rem;
box-sizing: border-box;
}

body > header h1 {
max-width: 100%;
margin: 1rem auto;
}

/* Format navigation */
nav {
border-bottom: 1px solid var(--border);
font-size: 1rem;
line-height: 2;
padding: 1rem 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
padding-bottom: 2rem;
}

nav a {
margin: 1rem 1rem 0 0;
color: var(--text) !important;
padding: 0.1rem 1rem;
}

nav a:hover {
color: var(--accent) !important;
}

nav a:last-child {
margin-right: 0;
}

/* Reduce nav side on mobile */
@media only screen and (max-width: 750px) {
nav a {
border: none;
padding: 0;
color: var(--accent);
text-decoration: underline;
line-height: 1;
}
}

/* Add a little padding to ensure spacing is correct between content and nav */
main {
padding-top: 1.5rem;
}

body > footer {
margin-top: 4rem;
padding: 2rem 1rem 1.5rem 1rem;
color: var(--text-light);
font-size: 0.9rem;
text-align: center;
border-top: 1px solid var(--border);
}

/* Format headers */

h1 {
font-size: 3rem;
}

h2 {
font-size: 2.6rem;
margin-top: 3rem;
}

/* Reduce header size on mobile */
@media only screen and (max-width: 720px) {
h1 {
font-size: 2.5rem;
}

h2 {
font-size: 2.1rem;
}
}

/* Format links */
a,
a:visited
{
color: var(--accent);
}

a:hover {
text-decoration: none;
}

This is a stripped down version of Simple.css. Thanks KevQ.

Now we need to include the style sheet in our base.njk layout file. Open it up and add <link rel="stylesheet" href="/css/styles.css" />

_includes/base.njk:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/styles.css" />
<title>{{ title }}</title>
</head>

<body>
<header>{% include 'header.njk' %}</header>

<nav>{% include 'navigation.njk' %}</nav>

<main>
<h1>{{ title }}</h1>
{{ content | safe }}
</main>

<footer>{% include 'footer.njk' %}</footer>
</body>

You would have noticed that the stylesheet hasn't been applied, we have to a couuple more things in .eleventy.js, something called file passthrough copy.

Open .eleventy.js in VSCode and add the following:

module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("./src/css");

return {
passthroughFileCopy: true,
dir: {
input: "src",
output: "public",
includes: "_includes",
},
};
};

INFO: PassthroughCopy is used for passing through static asset files such as stylesheets, images, fonts, and javescript files.

Because this will come up we may as well create the directories and add in the configuration for our images, fonts and javascript files.

Create the following directories in src:

mkdir img && mkdir fonts && mkdir js

Update .eleventy.js again:

module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("./src/css");
eleventyConfig.addPassthroughCopy("./src/img");
eleventyConfig.addPassthroughCopy("./src/fonts");
eleventyConfig.addPassthroughCopy("./src/js");

return {
passthroughFileCopy: true,
dir: {
input: "src",
output: "public",
includes: "_includes",
},
};
};

Just make sure you put all your static files in the appropriate directory and you'll be good.

So finally, if you've kept 11ty running and the broswer running it should look like this:

A view of what your blog page should look like with the stylesheet applied

Amazing! Now if you're happy with that you can take the contents of the public directory and upload those to Neocities, however, the key thing in this is automating the rest with Github and the Neocities API - check again soon for the next parts where we will do just that.


Reference: I created this guide based heavily on the existing guides:

Without these, I wouldn't even know how to write down what I needed to.


  • First published Feb 06, 2022
  • Last updated Mar 16, 2022