File Paths Explained: Absolute, Relative, and Why 'File Not Found' Happens
Cannot find module './components/Button'? ENOENT: no such file or directory? Most of the time it's a path problem. How file paths work on Mac, Linux, and Windows, absolute vs relative paths, ./ and ../, the working directory, case sensitivity, and import aliases like @/.
Cannot find module './components/Button'. ENOENT: no such file or directory. 404 on an image that's definitely there. A large share of beginner errors come down to one thing: a file path that points somewhere slightly different from where the file actually is.
What a path is
A path is the address of a file or folder: the list of folders you walk through to reach it, separated by slashes.
/Users/ana/projects/plant-tracker/src/components/Button.tsx
macOS and Linux use forward slashes /, and everything starts from /, the root.
Windows traditionally uses backslashes and drive letters:
C:\Users\ana\projects\plant-tracker\src\components\Button.tsx
Most development tools on Windows also accept forward slashes, and in code — imports, config files — always use forward slashes. They work everywhere.
Absolute paths
An absolute path is the full address, starting from the root:
/Users/ana/projects/plant-tracker/.env
C:\Users\ana\projects\plant-tracker\.env
It means the same thing no matter where you are. But absolute paths don't belong in your project's code: they only exist on your computer. Deploy the app and /Users/ana/... doesn't exist on the server. That's a classic "works locally, breaks in production" bug. (Why Does My App Work Locally but Not in Production?.)
Relative paths
A relative path is directions from where you are now:
| Path | Means |
|---|---|
Button.tsx or ./Button.tsx |
in this folder |
./components/Button.tsx |
in the components folder inside this one |
../Button.tsx |
in the folder one level up |
../../lib/utils.ts |
two levels up, then into lib |
. means "this folder." .. means "the parent folder."
In code imports, relative paths are relative to the file doing the importing:
// in src/app/dashboard/page.tsx
import { Button } from "../../components/Button"
// → up to src/app, up to src, into components
Count the folders carefully. Off-by-one ../ is the most common cause of Cannot find module.
Import aliases: @/
Deep ../../../ chains are hard to read and break when files move. Many projects — including most Next.js projects — set up an alias so @/ means "the src folder" (or the project root):
import { Button } from "@/components/Button"
The alias is defined in tsconfig.json (or jsconfig.json):
{
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}
If @/ imports fail, check that this setting exists and points at the right folder — and that any other tool involved (a test runner, a bundler) knows about the alias too.
The working directory
When a program runs, it has a current working directory: the folder it considers "here." In the terminal, it's the folder you cd'd into. (The Terminal for Beginners.)
pwd # print working directory (macOS/Linux; also works in PowerShell)
Paths in code that reads files at runtime are often relative to the working directory — not to the source file:
fs.readFileSync("data/plants.json")
// looks in <working directory>/data/plants.json
Run the app from a different folder and it breaks. Safer: build the path from the source file's own location:
import { fileURLToPath } from "node:url"
import path from "node:path"
const here = path.dirname(fileURLToPath(import.meta.url))
const file = path.join(here, "data", "plants.json")
path.join also handles the / vs \ difference between systems for you.
Case sensitivity
This one causes endless confusion:
- macOS and Windows usually treat
Button.tsxandbutton.tsxas the same file. - Linux — which is what almost every server runs — treats them as different.
So import Button from "./button" works on your Mac, and fails on the server with Cannot find module. Always match the exact capitalisation of file names. If you rename a file only by changing case, Git may not notice on Mac or Windows; use git mv button.tsx Button.tsx.
Paths on the web
URLs have paths too, and the same ideas apply: /images/logo.png starts from the site's root; images/logo.png is relative to the current page — which breaks when you're on /blog/post-1. In most frameworks, files in a public/ folder are served from the site root: public/logo.png is at /logo.png. (Anatomy of a URL.)
Spaces in paths
In the terminal, a space separates arguments, so paths with spaces need quotes:
cd "My Projects/plant tracker"
Better still, avoid spaces in project folder and file names altogether.
Debugging a "not found" error
- Read the path in the error. What exactly was it looking for?
- Check the file exists at that exact path, with that exact capitalisation.
- Check where you're starting from — the importing file's folder, or the working directory.
- Check aliases in
tsconfig.json. - Check file extensions — some setups need
.jsin imports; others don't.
EasySpawn workspaces run on Linux — the same case-sensitive filesystem your production server uses — so path bugs show up while you're building, not after you deploy. See how it works or join the waitlist.
Related: The Terminal for Beginners · How to Read an Error Message · Debugging for Beginners
Keep reading
Regular Expressions for Beginners: Reading Regex Without Panic
^[\w.+-]+@\w+\.\w{2,}$ looks like a cat walked on the keyboard. It's a regular expression, and AI tools write them constantly. The dozen symbols that cover most regex, how to read one piece by piece, how to test them, and when not to use regex at all.
Merge Conflicts Explained: What They Are and How to Fix Them
CONFLICT (content): Merge conflict in app.js looks alarming, but it's Git asking you a simple question. Why conflicts happen, how to read the <<<<<<< and >>>>>>> markers, how to resolve one in VS Code or with an AI tool, how to back out safely, and how to avoid most of them.