Site doc: Using Github to deploy a site made with 11ty to Neocities
Posted: 2025-10-12
Out of boredom I'm going to write about how I did some of the stuff I did on my site. You can reference this and I hope this can help you, but remember that not everyone's use case is exactly the same, so always make sure you know what a code is doing before copying!
I have not clicked open the "edit site" button on Neocities in ages; I only open it when I want to check how much space I'm using. Whenever I make an edit to my site, I just push the commit to the main branch and Github handles the rest. Here's how I did that.
Contents
Git
PetraPixel had made wonderfully-explained tutorials on a lot of these things already, so I recommend checking out their Git tutorial, NPM tutorial, and 11ty tutorial before reading this!
Git is one of the most popular version control systems nowadays. That means it's a tool for keeping track of older versions of your codebase so that if you screw up, you can revert back to an old version. Branches are also helpful. You can create a branch for a new part of your site, or perhaps a redesign, and if you screw up you just go back to the main branch and delete the new branch.
I also have Github Desktop downloaded, which is a GUI that makes things easier. This is back when I was in China and pushing and pulling took ages and I didn't know how to configure proxies (still don't). My VPN proxies all desktop applications automatically so it made things way faster. Now I won't be needing it since VSCode has a GUI for Git too and it's not as slow as before.
11ty
11ty is a static site generator (SSG), meaning it takes a bunch of template files, fills in the templates, and outputs a bunch of HTML files for the browser to read.
Static site generators are great for bigger sites. Suppose I want the same header and footer on every page. I can copy / paste stuff, but what if I want to update the header? I'd have to update everything! And don't even bring up iframes in front of me. They're like loading one page for the price of two. They're only good for embedding another website in yours, like Youtube videos, guestbooks, etc.
I've also recently learned about JavaScript custom elements (components), which could be worth checking out if you don't mind a lot of client-side JS and don't want to go all SSG. But SSGs still offer greater flexibility than simply components. With 11ty, you can
- Make a layout so you have the same header and footer in every page, as well as all the
<meta>
tags- Wrap a layout inside another layout for specific categories of pages that have their own shared sections
- Do pagination with data, which is how I made my character pages
- Use data for literally anything, which is how I made many little games with the same character data file
- Automatically convert all your images to a certain size and format
- Use any templating language you want
When I started out I made my site with the Liquid templating language, but it's quite stupid to work with and now my site's too big for me to switch to something else. If I were to make a new site, I would use Vento.
Why yes, I am shilling for 11ty. If you don't use it yet, and you're thinking your site's starting to get disorganized… Scroll to the top and click on PetraPixel's tutorials!
Deploy to Neocities with Github Actions
This is the good part. Because sites generated by SSGs require a build step, that is, you need to run npm run build
and check in the _site
folder for the actual contents of the website, it means you can't directly dump your source code and expect the site to function. Github provides a great tool for you to run these build steps on their computers called Github Actions.
Before doing anything, you need to enable them in Github by going to Settings > Actions > General and checking "Allow all actions and reusable workflows."
There are many things Github Actions are capable of which I don't know because I don't use them so I don't care. But each action is a .yml
file stored inside the .github/workflows/
folder of your repository.
Each action has a trigger, the thing that makes the action run. If you want the build to run every time you update the site, the most common trigger would be on push — whenever local changes are uploaded to the remote. You'll also want it to trigger only when it's pushed to the main branch too, or your test changes on a test branch can get published and you don't want that. So at the start of build.yml
, there's usually this:
name: Build Eleventy
on:
push:
branches:
- main
For me, I have a stupid birthday message system (which I may write about in the future) that requires one build per day to function, which this can do too! Just add another trigger that's a schedule, and schedule it to trigger once every midnight (UTC).
name: Build Eleventy
on:
push:
branches:
- main
schedule:
- cron: '0 0 * * *'
The weird cron
property is a code for time. I don't understand it either; I grabbed it from here.
Before you start deploying your site to Neocities, you need to access its API. Go to Settings > Manage site settings > API, and generate an API key. Don't show this to anyone else! People can edit your site with this; that's the whole point. Go to your Github repository, click Settings > Secrets and variables > Actions. These are the secrets that Github Actions can use. Click "New repository secret," name it "NEOCITIES_API_KEY," and set its value to the API key you generated.
The rest is copy work. Here's how your file might look, if you're deploying to Neocities.
name: Build Eleventy
on:
push:
branches:
- main
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js current
uses: actions/setup-node@v3
with:
node-version: current
- name: Install dependencies & build
run: |
npm ci
npm run build
- name: Deploy to neocities
uses: bcomnes/deploy-to-neocities@v3
with:
api_token: ${{ secrets.NEOCITIES_API_KEY }}
cleanup: true
dist_dir: _site
cleanup: true
deletes the files that are on Neocities but not in the _site
folder. Set this to true, or otherwise old files you've deleted will still appear on your site!
There are three steps here. The first step makes the computer use Node.js. The second builds your 11ty site. Make sure you do have a script called build
in your package.json
file! If not, simply add this in:
"scripts": { // the "scripts" object should already exist. If not then just add it too.
"build": "eleventy"
}
And the final step uploads everything in the _site
folder to Neocities.
It's important to not include any file that's not in the list of approved file formats, or the action fails!
Deploy to both Neocities and Github Pages
I have my site mirrored in both these places. I just need another step in my action file to deploy it to Github Pages.
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: _site
publish_branch: gh-pages
(The GITHUB_TOKEN
secret comes automatically.)
In Github, go to Settings > Pages and enable Github Pages deploying from the branch gh-pages
. You might need to let this action run (and fail) once first before being able to select that branch.
Deploying to a subdirectory
But there's a problem! Unless your repository is named tofutush.github.io
, Github Pages will deploy the contents of this site to a subdirectory, tofutush.github.io/The-Iron-Ragdoll
. In this case, all of the absolute links in your site will break. You link /
in hopes that it will take you to the home page tofutush.github.io/The-Iron-Ragdoll
, but it would take you to tofutush.github.io
instead. How do we solve this problem without resorting to using relative links everywhere?
11ty already provides a solution. When running eleventy
, you can pass in a pathprefix
variable that's exactly what the The-Iron-Ragdoll
in the URL is. So if I run eleventy --pathprefix=The-Iron-Ragdoll
, 11ty will host your site in localhost:8080/The-Iron-Ragdoll
.
And then, we need to transform every absolute URL into /The-Iron-Ragdoll/
too. The plugin comes straight with 11ty so you don't need to install it yourself.
// .eleventy.js
import { EleventyHtmlBasePlugin } from '@11ty/eleventy';
export default function (eleventyConfig) {
eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
}
(Add the addPlugin
line into your config function if it exists (which is most likely). Don't create another function!)
With that, you can make a new build script, one for Neocities and one for Github Pages.
"scripts": {
"build": "eleventy --pathprefix=The-Iron-Ragdoll",
"buildneo": "eleventy --output=_neocities"
}
The buildneo
script changes the output directory from _site
to _neocities
so I don't override things.
This is my final action file.
name: Build Eleventy
on:
push:
branches:
- main
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js current
uses: actions/setup-node@v3
with:
node-version: current
- name: Install dependencies & build
run: |
npm ci
npm run build
npm run buildneo
npm run copy
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: _site
publish_branch: gh-pages
- name: Deploy to neocities
uses: bcomnes/deploy-to-neocities@v3
with:
api_token: ${{ secrets.NEOCITIES_API_KEY }}
cleanup: true
dist_dir: _neocities
(There is a line, npm run copy
, that copies my images from _site/img
to _neocities/img
. You probably don't need it!)
There we go. Don't we all love this automatic shit.
Other minor stuff
eleventyConfig.setQuietMode(true);
This gets rid of all the lines telling you which file had been written.
eleventy --serve --incremental --pathprefix=The-Iron-Ragdoll
This is my serve script. The --incremental
flag makes sure that when I change a file, only that file will be updated, instead of the entire site.
return {
dir: {
input: 'tir',
includes: '../_includes',
layouts: '../_layouts',
data: '../_data'
},
passthroughFileCopy: true
};
This is the return object of my 11ty config function. input: 'tir'
puts all my input files in the tir
folder instead of at the root folder, so I can open that folder in Obsidian and it won't have too many extra folders and files I don't want to see while editing my site. If you did that though, you have to set includes
, layouts
, and data
to the parent directory, because 11ty will still be looking at them relative to tir
.
I also separated the layouts and include files because there were too many of them.
That's it, I think! I hope you find this helpful!