WasiLearn Code
HTML · Links, lists and images

File paths: why your image vanished

Your page works. The photo shows, the stylesheet applies, every link opens. You upload it to a server, load the same page on the same laptop, and the photo is a broken icon with a bit of alt text next to it.

Nothing about the file changed. What changed is who is resolving the path.

A path is directions, not an address

When you write src="photo.jpg", you have not told the browser where the file is. You have told it how to get there from the page it is currently reading. The browser takes the current page's address, throws away the file name at the end, and joins your path onto what remains.

Say the browser is showing https://example.com/canteen/menu.html. It strips menu.html, leaving https://example.com/canteen/, and asks the server for https://example.com/canteen/photo.jpg.

That is the whole mechanism. Every path bug in this lesson is a case of the starting point not being where you assumed.

Going down into a folder

Real sites do not keep everything in one folder. Here is a small one:

site/
  index.html
  menu.html
  images/
    biryani.jpg
    chai.jpg
  styles/
    main.css

From index.html, a file inside images/ is reached by naming the folder, then a slash, then the file:

<img src="images/biryani.jpg" alt="A plate of biryani with raita">
<link rel="stylesheet" href="styles/main.css">

No slash at the front. images/biryani.jpg means "from here, go into images". Read it left to right as directions and it stays clear.

Note there is nothing special about the names images or styles — they are folders you made up. The browser has no idea what they are for.

../ goes up one level

Now suppose the site grows and you put the menu pages in their own folder:

site/
  index.html
  images/
    biryani.jpg
  styles/
    main.css
  menu/
    lunch.html
    dinner.html

From menu/lunch.html, writing href="styles/main.css" asks for site/menu/styles/main.css, which does not exist. You are one level too deep, so you have to climb out first. .. means the folder above this one:

<link rel="stylesheet" href="../styles/main.css">
<img src="../images/biryani.jpg" alt="A plate of biryani with raita">
<a href="../index.html">Back to the front page</a>

Read ../styles/main.css as: go up out of menu, into styles, take main.css.

You can stack them. From a file two folders deep, ../../index.html climbs twice. And dinner.html sitting next to lunch.html needs no prefix at all — href="dinner.html" is enough, because they share a folder.

This is where most path confusion starts. The path is written relative to the file that contains it, not relative to the site, and not relative to the page the reader came from. Move lunch.html into a different folder and every path inside it has to be recounted.

Root-relative paths start from the top

There is a way to stop counting. A path beginning with a single / is root-relative: it starts at the root of the site, wherever the current page happens to be.

<link rel="stylesheet" href="/styles/main.css">
<img src="/images/biryani.jpg" alt="A plate of biryani with raita">
<a href="/index.html">Back to the front page</a>

Those three lines are correct in index.html, in menu/lunch.html, and in a page five folders deep. That makes root-relative paths the sensible choice for things every page needs — the stylesheet, the logo, the navigation links.

The catch is the reason this lesson exists in the first place. Root-relative paths need a server. If you open a file by double-clicking it, the address is something like file:///D:/Projects/site/menu/lunch.html, and the "root" the browser resolves /styles/main.css against is the root of your entire hard disk — D:/styles/main.css. Nothing there.

So /styles/main.css can fail locally and work when deployed, while ../styles/main.css works in both. If you want to use root-relative paths, run a local web server rather than opening files directly. Most editors have one built in, and Python ships with one:

python -m http.server

Then load http://localhost:8000/ instead of double-clicking.

Windows says the case does not matter. The server disagrees.

This is the bug that ships. It passes every test you run, and breaks the moment real users load the page.

You save a photo from your phone. It arrives called Photo.JPG, with a capital P and a shouting extension. You write the tag from memory:

<img src="images/photo.jpg" alt="The canteen counter at lunchtime">

On your laptop, it works. The photo appears. You look at it and move on.

Windows and macOS both use file systems that are case-insensitive by default: ask for photo.jpg and you get Photo.JPG, because as far as those systems are concerned they are the same name. Your browser asks the file system for a file, the file system hands one over, everyone is happy.

Then you deploy. Almost every web server in the world runs Linux, and Linux file systems are case-sensitive. Photo.JPG and photo.jpg are two different names, in exactly the way Bilal and bilal are two different words. The server looks for photo.jpg, finds no such file, and returns a 404. Your page shows a broken image to everyone except you.

The same thing happens to stylesheets, which is worse, because a missing stylesheet does not leave a broken icon behind. The page just renders as unstyled black text on white and you assume you have broken your CSS.

Worse still, this bites when you rename. Change photo.jpg to Photo.jpg on Windows and Git may not even notice the change, because Git is asking the same case-insensitive file system whether anything is different. You commit, deploy, and the server still has the old name — or the new one, and now your HTML is wrong.

The fix is a habit, not a setting:

  • Lower case everything. File names, folder names, extensions. Every time.
  • No spaces. Use a hyphen: chai-stall.jpg, not chai stall.jpg. Spaces in a URL become %20, which is legal but ugly and easy to get wrong.
  • Copy the name, do not type it. Most of these bugs are typed from memory by someone who was sure.
  • Rename files the moment you add them, before any HTML points at them.

Two paths that look almost identical:

<img src="images/Photo.JPG" alt="The canteen counter at lunchtime">
<img src="images/photo.jpg" alt="The canteen counter at lunchtime">

If your file is called photo.jpg, only the second one loads on a Linux server. There is no browser setting that rescues the first, and there is no warning while you develop.

Reading a broken path in the browser

When something does not load, open the browser's developer tools and look at the Network tab. A failed request shows up in red with a 404 next to it, and the URL it tried. That URL is the truth: it tells you exactly where the browser looked, which is usually enough to see where your counting went wrong.

If the URL contains file:/// and a path off your hard disk, you have a root-relative path and no server. If it contains a folder name twice — site/menu/menu/lunch.html — you have a relative path where you meant to go up first. If it looks perfect, check the capital letters.

Which kind to use

For links between pages in a small site, relative paths are fine and they work without a server. For shared assets — stylesheets, logos, the main navigation — root-relative paths save you from recounting ../ every time you move a file, provided you are running a server locally. Absolute URLs with a domain belong only to other people's sites.

Whichever you choose, be consistent within a project. A site with a mixture is a site where nobody can tell at a glance whether a path is broken.

Next, images covers what goes in that src and why alt is not optional. If a path in an href is what is failing you, the links lesson covers the <a> element itself.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes