Repo Setup: User-Site vs Project-Site, Branches, and the Deploy Source
Module 5 · Chapter 2 - GitHub Pages and a custom domain
What you’ll learn
- The difference between a user (or organisation) site and a project site, and which one a personal blog wants.
- How
url:andbaseurl:in_config.ymlinteract with the served URL, and when each matters. - The three deploy sources Pages supports: a branch, a
/docsfolder, or GitHub Actions. - Why
gh-pagesis a leftover convention rather than a requirement. - Where to find your build logs after the first push.
Concepts
GitHub Pages has two repo shapes, and the rules differ between them. A user site lives in a repository named exactly <username>.github.io (the same applies for organisations: <orgname>.github.io). You get exactly one per account. It serves from the apex of that hostname - https://<username>.github.io/ - and is the right shape for a personal engineering blog. A project site lives in any other repo and serves at https://<username>.github.io/<repo>/. Project sites are useful for project documentation but introduce a path prefix that complicates URL handling. The full rules are at docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages.
That path prefix is what baseurl: exists for. In Jekyll’s _config.yml, url: is your scheme-and-host (https://example.com), and baseurl: is the path your site lives under ("" for a user site, "/my-project" for a project site). Jekyll uses both to build internal links via the relative_url and absolute_url filters. Get baseurl wrong on a project site and every link 404s; get it wrong on a user site and you’ll prepend a path that shouldn’t be there. Once you point a custom domain at the repo (Chapter 5.4), url: becomes your domain and baseurl: returns to empty - the apex serves the site directly regardless of repo name.
The deploy source is the second decision. Under Settings → Pages → “Build and deployment”, you tell GitHub where to find what to publish. The three options are: deploy from a branch (and pick which one), deploy from a /docs subfolder on a branch, or deploy from GitHub Actions. The branch option triggers GitHub’s built-in Jekyll build whenever that branch updates. The /docs option does the same but only looks at docs/ - handy if your repo is primarily code with documentation alongside it. The Actions option turns off the built-in build entirely and lets a workflow you control upload the site (Chapter 5.3 walks through writing one).
You will see gh-pages mentioned a lot in older tutorials. It used to be the conventional branch name for a project site’s deploy source - your main held source, gh-pages held the built _site/, and a script copied between them. GitHub kept the convention, so a branch literally called gh-pages is still picked up as a Pages source. But it is no longer required, and it predates both the built-in Jekyll build and Actions. For new repos, deploy from main if you’re using the built-in build (no separate output branch needed - GitHub builds for you), or deploy from Actions if you’re building yourself. Reach for gh-pages only if a tool you’re using assumes it.
The first deploy after enabling Pages is the moment to know where logs live. For the built-in build, GitHub still publishes the build under the Actions tab as a workflow run called “pages build and deployment” - it’s a managed workflow you didn’t write, but its logs are visible. For the Actions deploy source, your own workflow’s run is what you read. In both cases, a failed build will not publish - the previous successful _site/ keeps serving until a green build replaces it.
Walkthrough
For a user-site blog, create the repo with the exact name <username>.github.io. Inside it, set up _config.yml to match what GitHub will serve before you have a custom domain:
# _config.yml - user site, no custom domain yet.
title: "Your Name - Engineering Notes"
description: "Notes on systems, performance, and things that broke."
# Scheme + host of where the site is served from.
# For a user site without a custom domain, this is the github.io hostname.
url: "https://your-username.github.io"
# baseurl is the path prefix. User sites serve at the apex, so leave empty.
baseurl: ""
# Jekyll permalink and posts settings unchanged from Module 3.
permalink: /:year/:month/:slug/
Push to main and enable Pages from the GitHub UI:
Settings → Pages → Build and deployment
Source: Deploy from a branch
Branch: main / (root)
Save
GitHub kicks off the “pages build and deployment” workflow. Watch it in the Actions tab. When it goes green, your site is at https://your-username.github.io/. If you don’t want the built-in build (because of Chapter 1’s safelist limits), switch the Source to GitHub Actions and skip ahead to Chapter 5.3 - that change disables the legacy build and waits for your own workflow to publish.
Compare the project-site shape, in case you’re publishing documentation rather than a personal blog:
# _config.yml - project site at https://your-username.github.io/my-project
url: "https://your-username.github.io"
baseurl: "/my-project" # MUST match the repo name; leading slash, no trailing slash
Then, in your templates, always generate links with relative_url:
<a href="{{ '/about/' | relative_url }}">About</a>
<link rel="stylesheet" href="{{ '/assets/main.css' | relative_url }}">
The filter prepends baseurl for you. Hardcoded paths like <a href="/about/"> will appear correct on a user site and break on a project site - a one-line refactor that is much easier to do once than to fix per-page later.
How it fits together
flowchart LR
push[git push] --> source{deploy source}
source -->|branch| builtin[built-in Jekyll build]
source -->|"/docs folder"| builtin
source -->|Actions| your_workflow[your workflow]
builtin --> serve[_site served at url + baseurl]
your_workflow --> serve
The push is the same; the deploy source decides whether GitHub builds or you do.
Common pitfalls
| Pitfall | Why it happens | Fix |
|---|---|---|
| All links 404 on a project site, but home loads | baseurl is unset or wrong, so /assets/main.css resolves at the apex, not under the repo path. |
Set baseurl: "/repo-name" and pipe paths through relative_url. |
Repo named my-blog, expected to serve at the apex |
Only <username>.github.io repos serve at the apex; everything else is a project site. |
Either rename the repo to <username>.github.io or accept the path prefix. |
Pushed to main, but Pages still shows the old site |
The deploy source is still pointing at a different branch (e.g. gh-pages). |
Settings → Pages → set Branch to main. |
| Build failed and you can’t find the logs | The “pages build and deployment” workflow run sits in the Actions tab - easy to miss because you didn’t write it. | Open Actions, filter by workflow pages build and deployment, click the failing run. |
| Switched Source to “GitHub Actions” but the site still serves the old version | You disabled the built-in build but haven’t pushed an Actions workflow yet; nothing new has published. | Either revert to a branch source or add the workflow from Chapter 5.3. |
Exercises
- Create a repo named
<your-username>.github.io. Push a minimal Jekyll site and enable Pages frommain. Confirm the site is live athttps://<your-username>.github.io/and find its build log in the Actions tab. - Create a second, throwaway project repo (any name). Enable Pages, then deliberately leave
baseurlempty and observe how stylesheets and links break. Fix it by settingbaseurland updating one link to userelative_url. - In your user-site repo, switch the deploy source to “GitHub Actions” temporarily. Observe that no new build runs and the previously served
_site/continues to serve. Switch back to the branch source - you’ll come back to Actions in the next chapter.
Recap & next
- User site (
<username>.github.io) serves at the apex withbaseurl: ""; project sites serve under a path and needbaseurl: "/repo". url:is the host,baseurl:is the path prefix;relative_urlandabsolute_urlfilters use both.- Deploy source is set under Settings → Pages and is one of: a branch, a
/docsfolder, or GitHub Actions. gh-pagesis a legacy convention; new repos rarely need it.- Build logs for the built-in build live in the Actions tab as the managed “pages build and deployment” workflow.
Next, Building with GitHub Actions for full plugin support - write the workflow that frees you from the safelist.
Check your understanding
Answer the questions below to test what you just read. You can change answers and resubmit; your best score is saved on this device.
Best score so far: /