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

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!


<==

Home

==>