Courses Learn Blog Contact
Get Started →
Lesson 18

Day 18: Shipping It

Deploying to a real https URL, the first-deploy bugs everybody hits, what a build step does and does not hide, a README worth reading, and Lighthouse.

Webbo3 Level 2: JavaScript Development 11 min read Free

Webbo3 Level 2 · JavaScript Development · Day 18 of 20

Shipping It

Getting your project off your laptop and onto a URL you can send to somebody who has never met you.

By the end of today you can

  • Deploy a static site to a real URL over https
  • Say what a build step actually does and why it exists
  • Keep configuration out of code and secrets out of the repository
  • Write a README a stranger can follow
  • Read a Lighthouse report and fix what it finds

"It works on my laptop" is not a finished project.

Until somebody else can open it on their own phone, on their own connection, you do not know that it works. You know that it works in the one place where every file is local, every path is right and the network is perfect.

1. Development and Production Are Different Places

Definition

Development is where you build: your own machine. Production is where real people use it: a server with a real address. Code that works in one can fail in the other, because the two places are not the same.

Real-Life Example

A dish you cook perfectly in your own kitchen, with your own pot, your own gas and your own spices. Cook it for a wedding in a hall kitchen you have never seen and something will be different: the pot, the heat, the missing seasoning.

Types: How They Differ

DevelopmentProduction
WhereYour machine, localhostA server, a real domain
FilesSeparate and readableOften bundled and minified
ErrorsFull detail in your consoleLogged, never shown to the user
DataTest records you inventedReal people's real data
NetworkInstantWhatever the user has
Who sees a crashYouEverybody

The Bugs That Only Appear in Production, Most Common First

  1. A path that works locally and not when served: /css/style.css against css/style.css.
  2. A file name whose capitalisation differs, which your laptop forgives and a Linux server does not.
  3. A mixed-content block: one asset loaded over http on an https page, which the browser refuses.
  4. An API that allowed localhost but not your new domain (CORS, Day 8).
Picture This

Picture the wedding hall kitchen: same recipe, different pot. You only find out what is different by cooking there.

2. Deploying a Static Site

Definition

A static site is files the server hands over exactly as they are: HTML, CSS, JavaScript and images, with no server code running. Deploying is copying those files to a server with a public address. Your projects are static, which is the easiest thing in the world to host.

Real-Life Example

Printing flyers is building. Pinning them on the notice board at the bus stop is deploying: the same paper, now where strangers can see it.

Types: Three Free Hosts

HostHowGood for
GitHub PagesRepository Settings, Pages, choose the branchSimplest. Already where your code is
NetlifyConnect the repository, or drag the folder inRedirects, forms, a preview of every pull request
VercelConnect the repositorySame idea, strong with frameworks later
What this code doesWe want the whole GitHub Pages deploy: push the code, then point Pages at the branch.
terminal
1
2
3
4
5
6
7
8
9
# Commit and push your finished project
git add .
git commit -m "Add README and deploy configuration"
git push

# Then on GitHub: Settings -> Pages -> Source: Deploy from a branch
#   Branch: main, folder: / (root) -> Save
# Within a minute or two the site is live at:
#   https://<your-username>.github.io/<repository-name>/
NOTE   
Replace <your-username> and <repository-name> with your own. Pages always serves over https.

The First Deploy Is Nearly Always Broken the Same Way

A blank page with 404 errors in the console means your paths are absolute. A path starting with / means "from the root of the domain". On GitHub Pages the root of the domain is not your project folder, because your project lives one folder down, under its repository name.

new URL(path, pageAddress) works out where a path points from a given page, using the same rules the browser uses when it reads your <link> and <script> tags.

What this code doesWe want to see exactly which address the browser requests for an absolute path and for a relative one, on a real GitHub Pages address.
app.js
1
2
3
4
5
6
7
// Your page, as GitHub Pages serves it
const page = 'https://chidinma.github.io/job-tracker/index.html';

// Where the browser will look for each way of writing the path
console.log('/css/style.css  ->', new URL('/css/style.css', page).href);
console.log('css/style.css   ->', new URL('css/style.css', page).href);
console.log('./js/app.js     ->', new URL('./js/app.js', page).href);
OUTPUT   TERMINAL
➜ webbo3 node app.js
/css/style.css  -> https://chidinma.github.io/css/style.css
css/style.css   -> https://chidinma.github.io/job-tracker/css/style.css
./js/app.js     -> https://chidinma.github.io/job-tracker/js/app.js

The absolute path lost /job-tracker and points at a file that does not exist. The relative paths stay inside your project. On localhost the two happen to point at the same place, which is why the bug hides until you deploy.

Capitalisation

Windows and macOS usually treat Logo.PNG and logo.png as the same file. The Linux servers that host your site do not. ls below asks a Linux filesystem for the file by the name your HTML uses.

What this code doesWe want to see a Linux server look for logo.png when the file is called Logo.PNG, the way your deployed site will.
terminal
1
2
3
4
5
6
7
8
9
10
mkdir -p site/images && cd site

# The file as it came off the camera
touch images/Logo.PNG

# The name your HTML asks for
ls images/logo.png

# The name that is really there
ls images/
OUTPUT   TERMINAL
ls: cannot access 'images/logo.png': No such file or directory
Logo.PNG

Before You Call the Deploy Done

  • Check every path is relative, including images and imports.
  • Check the capitalisation of every file name against the HTML that uses it.
  • Open the deployed site on your phone, on mobile data, not on your wifi.
  • Open the Console on the deployed site. It is not the same as your local one.
Try This

Deploy your Day 15 project to GitHub Pages now. If the page is blank, open the Console, copy the address of the first 404, and compare it with where the file really is.

Picture This

Picture the flyer at the bus stop: it only counts once a stranger walking past can read it.

3. What a Build Step Is

Definition

A build step is a program that turns the files you write into the files you deploy. You have not needed one yet. It is worth knowing what it does before Level 3 hands you one.

Real-Life Example

A publisher takes an author's manuscript, with its notes and crossings-out, and prints a neat, compact book. The story is the same; the form is made for readers, not for the author.

Types: The Stages

StageWhat happensWhy
BundleMany modules become a few filesFewer requests
MinifyWhitespace, long names and comments strippedSmaller download
TranspileNewer syntax rewritten to olderOlder browsers
Hashapp.js becomes app.8f3c1d.jsSafe caching: a new name is always fetched fresh
Inline envimport.meta.env.X replaced by its value at build timeConfiguration without editing code

The hash is a short fingerprint of the file's contents: change one character and it changes. sha256sum prints such a fingerprint, and cut -c1-6 keeps the first six characters.

What this code doesWe want to see why a hashed file name makes caching safe: the name changes the moment the contents do.
terminal
1
2
3
4
5
6
echo "console.log('version 1');" > app.js
echo "app.$(sha256sum app.js | cut -c1-6).js"

# Change one character and build again
echo "console.log('version 2');" > app.js
echo "app.$(sha256sum app.js | cut -c1-6).js"
OUTPUT   TERMINAL
app.c42b99.js
app.f8e58e.js
A Build Step Does Not Make a Secret Secret

Values inlined at build time end up in the bundle, which is a text file anybody can read. VITE_API_KEY in a frontend bundle is exactly as public as writing it in your HTML. The build step moved it; it did not hide it.

Picture This

Picture the publisher: the book is neater than the manuscript, and every word of it is still there for anyone to read.

4. Configuration That Changes Between Places

Definition

Configuration is the values that differ between development and production, such as the API address, or whether to print debug logs. They belong in one file, not scattered through the code.

Real-Life Example

A phone that switches to your home wifi at home and to mobile data outside. You do not re-set it every time you walk through the door; it looks at where it is and picks.

window.location.hostname is the domain the page was loaded from: 'localhost' on your machine, and your real domain once deployed. The config looks at it and picks.

What this code doesWe want one function that returns the right settings for wherever the page is running, and to check it for both places.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// Pick settings from the domain the page is running on
function configFor(hostname) {
  const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';

  if (isLocal) {
    return { apiBase: 'http://localhost:3000', pageSize: 10, debug: true };
  }
  return { apiBase: 'https://api.webbo3.com', pageSize: 10, debug: false };
}

// The same code, in both places
console.log('on your laptop:', configFor('localhost'));
console.log('deployed:      ', configFor('chidinma.github.io'));
OUTPUT   TERMINAL
➜ webbo3 node app.js
on your laptop: { apiBase: 'http://localhost:3000', pageSize: 10, debug: true }
deployed:       { apiBase: 'https://api.webbo3.com', pageSize: 10, debug: false }

In the real project this lives in config.js and exports the result for the current page. Nothing else in your codebase contains a URL: when the API moves you change one line.

What this code doesWe want to see the config pick its settings in a real browser page served from localhost, and use config.debug to keep noisy logs on your machine only.
index.html
1
<p id="api"></p>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Pick settings from the domain the page is running on
function configFor(hostname) {
  const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';

  if (isLocal) {
    return { apiBase: 'http://localhost:3000', pageSize: 10, debug: true };
  }
  return { apiBase: 'https://api.webbo3.com', pageSize: 10, debug: false };
}

const config = configFor(window.location.hostname);
document.querySelector('#api').textContent = `Talking to ${config.apiBase}`;

// Debug logs appear locally and never in production
if (config.debug) {
  console.log('debug: page loaded from', window.location.hostname);
}
BROWSERindex.html · served from localhost
CONSOLE   BROWSER DEVTOOLS
debug: page loaded from localhost
Picture This

Picture the phone choosing wifi or data by itself. The config chooses the API the same way, so you never edit a URL by hand before deploying.

5. The README

Definition

The README is the page GitHub shows under your code. It is the part students skip and employers read first, and often the only thing somebody looks at before deciding whether to open your code.

Real-Life Example

The back cover of a book in a bookshop. Nobody reads chapter one to decide whether to buy; they read the back.

What this code doesWe want a README a stranger can act on in thirty seconds: what it is, the live link, a picture, how to run it, and what comes next.
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Job Application Tracker

Track job applications and see at a glance which ones need chasing.

**Live:** https://chidinma.github.io/job-tracker/

![Screenshot of the tracker](docs/screenshot.png)

## Why I built it
I was losing track of which applications I had heard back from. Everything
lived in my head and in a WhatsApp message to myself.

## Features
- Add, edit and delete applications
- Filter by status, search by company
- Flags anything still "applied" after 14 days
- Saves in the browser, so it survives a refresh
- Works with a keyboard only; Lighthouse accessibility 100

## Running it locally
    git clone https://github.com/chidinma/job-tracker.git
    cd job-tracker
    npx serve .

It needs a server rather than opening the file directly, because it uses
ES modules.

## Built with
Vanilla JavaScript, no frameworks. ES modules, localStorage, CSS Grid.

## What I would do next
- Move storage to a backend so it syncs between devices
- Export to CSV
- Email reminders, which need a server

## What is deliberately out of scope
No accounts, no backend, no file uploads. One person, one browser.
NOTE   
GitHub renders this as the front page of the repository. Lines indented by four spaces show as a code block.
  • The live link goes at the top. Most readers will click it and never scroll.
  • A screenshot. Somebody scrolling twenty repositories will look at a picture and not read a paragraph.
  • "What I would do next" shows you know what is missing, which reads as judgement rather than as a gap.
Picture This

Picture the back cover in the bookshop: ten seconds, one picture, and a reason to open it.

6. Lighthouse Before You Call It Done

Definition

Lighthouse is a checker built into Chrome DevTools that scores a page on performance, accessibility, best practices and SEO, and lists what to fix. Open the Lighthouse tab, tick all four categories, and run it against your deployed URL, not localhost.

Real-Life Example

A roadworthiness test for a car. It does not tell you whether the car is beautiful; it tells you which specific things will fail on the road.

CategoryAim forUsually caused by
Performance90+Unresized images, render-blocking scripts
Accessibility95+Contrast, missing labels, missing alt
Best Practices95+Console errors, mixed content
SEO90+Missing title, description, or viewport meta
What this code doesWe want the head tags that fix the SEO score and turn a pasted link into a card with a title and a picture.
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<head>
  <meta charset="utf-8">
  <!-- Fit the page to the phone's width -->
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- What search engines and browser tabs show -->
  <title>Job Application Tracker</title>
  <meta name="description" content="Track job applications and see which ones need chasing.">

  <!-- What WhatsApp, LinkedIn and Slack show when the link is pasted -->
  <meta property="og:title" content="Job Application Tracker">
  <meta property="og:description" content="See at a glance which applications need chasing.">
  <meta property="og:image" content="https://chidinma.github.io/job-tracker/docs/screenshot.png">
  <meta property="og:url" content="https://chidinma.github.io/job-tracker/">
</head>
NOTE   
og:image must be a full https address, not a relative path, because the app showing the card reads it from outside your site.
Why the og: Tags Are Worth Five Minutes

They are what renders when you paste your link into WhatsApp, LinkedIn or Slack. Without them your portfolio project appears as a bare URL. With them it appears as a card with a title and a picture. You will be pasting these links into applications, so this is not decoration.

Picture This

Picture the roadworthiness test: a list of exactly what will fail on the road, before the road finds it for you.

Do This Now

  1. Deploy your Day 15 project and get a real https URL.
  2. Fix the first deploy. Expect broken paths and at least one capitalisation problem.
  3. Open it on your phone, on mobile data, and use it properly for two minutes.
  4. Write the README to the shape above, screenshot included.
  5. Run Lighthouse on the deployed URL. Get Accessibility above 95 and fix every console error.
  6. Send the link to somebody who has never seen it and watch them use it without helping. Write down every moment they hesitate.
That Last One Is the Whole Exercise

Watching one person use your project without your commentary will teach you more than any checklist on this page. Every hesitation is a design problem you could not see because you already knew the answer.

Checkpoint

Q1. Your deployed site is blank with 404s in the console. Most likely cause?

Absolute paths. /css/style.css points at the domain root, which is not your project folder on GitHub Pages. Use relative paths.

Q2. It works locally but an image 404s on the server. Why?

Capitalisation. Your laptop's filesystem usually ignores case and the server's does not, so Logo.PNG and logo.png are two different files there.

Q3. Does a build step make an API key safe?

No. Values inlined at build time are in the bundle, which is a text file anybody can read. It moved the key; it did not hide it.

Q4. Why run Lighthouse against the deployed URL rather than localhost?

Because localhost has no real network, no compression and no caching headers, so the performance numbers mean nothing. Some best-practice checks also depend on https.

Tick Before You Move On

  • ☐ My project is deployed and I have opened it on my phone on mobile data
  • ☐ Every path is relative and every file name matches its capitalisation
  • ☐ All settings that change between places live in one config file
  • ☐ The README has the live link, a screenshot and a what-next section
  • ☐ Lighthouse Accessibility is above 95, with zero console errors
  • ☐ Somebody else used it in front of me without my help

Quick Recap

It is not finished until a stranger can open it on their own phone. Relative paths and exact capitalisation, or the first deploy is blank. A build step bundles and inlines; it does not create secrets. The README is read before the code, so the live link and a screenshot go first. Run Lighthouse on the deployed URL, never on localhost.

Final Scenario

You send a recruiter one link. It opens on their phone in the back of a taxi, over a weak connection.

The page loads, the images fit, the paths all resolve, and the link they forward to their manager shows up as a card with your project's name and a screenshot.

None of that is code they will ever read. All of it decides whether they read your code.

Tomorrow, Day 19: turning four projects into something an employer can read in ninety seconds.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.