Two Ways To Open The Same File
file:///Users/you/projects/my-site/index.html
http://localhost:3000/index.html Both methods load the same file, same bytes, and same browser. In the first case the browser reads a file off the disk directly. In the second it sends a request over the network to a program that answers it, even though that program happens to be running on the same machine.
The word before the colon is the scheme, and it decides which of those two things happens. That one word is why the same page behaves differently in the two cases.
What Breaks Under file
Browsers apply security rules based on a page's origin, which is the combination of scheme, host, and port. Two pages share an origin when all three match, and code from one origin is restricted in what it can do to another.
http://localhost:3000/about/index.html
└┬─┘ └───┬───┘ └┬─┘
│ │ └── port
│ └───────── host
└─────────────────── scheme
The first three together are the origin. A page loaded from disk has no host and no port, so browsers treat it as having an opaque origin that matches nothing, including other files in the same folder. Several common things stop working as a result.
Fetching another file fails
A request to load a JSON file of content, or any other data the page needs, counts as a cross-origin request under file. The browser blocks it and reports a CORS error, which is confusing because there is no other site involved.
Module scripts fail
A script tag with type="module" is loaded under the same origin rules. Under file, the import is blocked. Older non-module scripts still work, which is why a page can half-function.
Absolute paths break
A link or image path starting with a slash means "the root of this site." Under file, that is the root of your hard drive, so the file is not found.
Storage and workers are limited
Service workers refuse to register, and browsers treat storage for opaque origins inconsistently. Behavior differs between Chrome and Firefox, which makes the symptom hard to search for.
🟠 Nothing looks broken, which is the hard part
The HTML renders and the CSS applies, so only the parts that needed to fetch something are missing. Because the reason is logged in the console rather than on the page, checking the console first is best when a page half-functions.
What A Development Server Is
A development server is a small program that watches a folder, listens for HTTP requests, and answers them with files from that folder. It is not a website host, and nothing on the internet can reach it. Its one job is to make your own files arrive over http instead of file, which gives the page a real origin and restores everything on the list above.
Most of them add conveniences on top: reloading the browser when a file changes, printing each request as it comes in, serving index.html when a folder is requested. Useful as those are, the origin is the reason you need the server at all.
localhost
This is a name that always means this computer. It resolves to 127.0.0.1, which is an address that never leaves the machine.
Not on the internet
Nobody else can open your localhost. Sending that address to a classmate points at their machine, not yours.
It holds the terminal
A server runs until stopped. You can stop the server by pressing Ctrl and C. The prompt not coming back is correct.
It serves, it does not build
A dev server hands out whatever is currently in the folder. Generating those files is a separate step that runs first.
Different from production
A real host serves the built output to the public over HTTPS, from a machine that is not yours. The dev server is a stand-in so you can see the result before that happens.
Any of them will do
The VS Code Live Server extension, a Node server package, and a server built into a framework all solve this the same way.
What A Port Number Is
One computer has one network address but runs many programs that want to talk over the network. A port number is how the machine knows which program an incoming request belongs to. You can think of the address as the building and the port as the apartment number.
Ports run from 1 to 65535. Some are conventional: 80 for http, 443 for https. Browsers assume those two, which is why a normal web address has no port in it. Development servers pick high numbers that nothing else claims, which is where 3000, 5173, 5500, and 8080 come from. Those four are conventions people copied from each other, and nothing about them is special.
One program per port
Two servers cannot listen on the same port on the same machine. The second one fails to start rather than sharing.
The error has a name
EADDRINUSE means the port is already taken, almost always by a server left running in another terminal tab from an earlier attempt.
The port is part of the origin
Port 3000 and port 5500 are different origins to the browser. A project configured to expect one will not be satisfied by the other.
Changing it is fine
Most servers take a flag for the port. A flag on the server command lets you select a free port when another program has yours.
Error: listen EADDRINUSE: address already in use 0.0.0.0:3000 There are two fixes: find the old terminal tab and stop that server with Ctrl and C, or start this one on a different port.
Try Breaking It On Purpose
The fastest way to make this stick is to watch the same files behave three different ways.
- You can serve a project and open it at its
localhostaddress to verify everything works. - If you stop the server and open
index.htmlby double-clicking it, any fetch calls will fail in the console. - If you open it with a different server on a different port, the page is served and fetches work again, but anything that hard-coded the first port will fail.
Nothing in the files changed across those three attempts. Only the delivery changed, and the delivery is part of what decides what the page is allowed to do.
Watching the console, if you have not before
Step two says to watch the console, which assumes you already know where it is. Here is HAP opening the console and reading the first red line. This video runs for about two minutes.