What happens when you navigate
Trace a browser navigation from typed input through network checks, renderer selection, page commit, service workers, and preload.
- 01Follow a navigationName the steps Chrome takes after you press Enter: read the input, send the request, check the reply, and pick a renderer.
- 02Explain commitSay why a new page becomes active only when a renderer commits it, and what changes then: the address bar and the history.
- 03Use navigation preloadSay when preload helps a service worker by starting the network request while the worker starts, and how to use its response.
A navigation is a chain of steps
You type a web address and press Enter. The new page seems to appear at once. In fact, the browser does several jobs first, one after another:
- It reads what you typed.
- It asks a server for the page. A server is the computer that holds the website.
- It checks the reply.
- It picks a renderer. A renderer is the part of the browser that shows a page.
- It makes the new page the active page.
After that, the new page keeps loading its HTML, scripts, images, and styles.
The browser calls one loaded web page a document. A navigation is the whole move from one document to another, in the same tab or frame. A commit is the moment the chosen renderer takes the new document as the active page.
When this lesson says in Chrome, it means Chrome's own published description of how it works inside. Other browsers are built differently inside, but they do the same jobs. The JavaScript in this lesson is a small model of these steps. It cannot look inside a real browser.
This lesson follows Browser architecture. That lesson showed how the browser splits its work into separate parts: the browser process, the renderer, the network part, and the GPU part. A process is a running program with its own memory. The next lesson is From HTML to running script. It starts once the new page's HTML reaches the renderer.
The browser reads what you typed
Start with a web address as plain JavaScript data. Line 1 makes a URL object. A URL object splits an address into named parts. Line 2 prints two of them:
hostname: the site's name.- The
itemquery value: the part after?item=in the address.
So it prints shop.example tea.
1const url = new URL("https://shop.example/cart?item=tea");2console.log(url.hostname, url.searchParams.get("item"));3 4function classifyInput(text) {5 return text.includes(".") || text.startsWith("http") ? "url" : "search";6}7 8console.log(classifyInput("how to make tea"));9console.log(classifyInput("shop.example/cart"));Line 4 starts a very small model of the address bar. Line 5 has one rule. If the text has a dot, or starts with http, it looks like an address, so the model says url. Any other text looks like a search.
So line 8 prints search for how to make tea. Line 9 prints url for shop.example/cart. A real address bar has many more rules. For example, it uses your search settings, fills in missing parts of an address, and runs safety checks.
In Chrome, the address bar belongs to the browser process. The browser process is the main program of the browser. It runs the tabs and the address bar. Inside it, the UI thread reads what you typed. A thread is one line of work inside a process, and UI means user interface: the buttons and the address bar.
The UI thread decides: is this a search or an address? This has to happen first. The renderer for the new page may not exist yet. The browser needs to know what to ask the network for before any page can get HTML.
You type what you want to eat. Then the app decides where to send the order. This first choice happens before anyone cooks or delivers anything.
- In real life: You type a dish or restaurant
- In JavaScript: You type a URL or a search
- In real life: The app contacts the restaurant
- In JavaScript: The browser starts a network request
- In real life: A delivery person is assigned
- In JavaScript: A renderer is chosen or started
- In real life: Food reaches your door
- In JavaScript: The new page commits and becomes active
The browser asks the server
When you press Enter, the browser starts a navigation request: it asks a server for the page. In Chrome, the UI thread starts this work. Then the network thread does the network jobs. Two of these jobs are:
- DNS lookup: finding the server's number address from its name.
- Setting up TLS (Transport Layer Security): the safe, scrambled connection that
httpsaddresses use.
While this happens, the tab shows that it is loading.
1const typed = "how to make tea";2const kind = typed.includes(".") ? "url" : "search";3const request = kind === "url" ? typed : "https://search.example/?q=" + encodeURIComponent(typed);4console.log(request);Line 1 stores the text the user typed. Line 2 decides it is a search, in this small model. Line 3 builds the request address. Line 4 prints https://search.example/?q=how%20to%20make%20tea.
encodeURIComponent() turns each space into %20. Without it, spaces and other special characters could break the address.
The request does not promise that this exact address will be shown. The server can answer with a redirect. A redirect is a reply that says "the page is at another address". In Chrome's own example, a 301 reply tells the network thread about a redirect, and a new request starts.
A redirect to another site can also change which renderer should show the page. So the browser does some steps at the same time. It does not wait for each step to finish before starting the next.
Step through the lesson's small teaching model of a navigation. It calls the real functions shown here. It cannot pause a real browser.
script
2const kind = classifyInput(typed);3const request = kind === "url" ? typed : "https://search.example/?q=" + typed;4const response = { status: 200, contentType: "text/html" };5 6function navigate(input) {7 const kind = classifyInput(input);8 const request = kind === "url" ? input : "https://search.example/?q=" + input;9 const decision = responseDecision(response.status, response.contentType);10 const renderer = decision.action === "render" ? "renderer ready" : "not needed";11 return { kind, request, decision: decision.action, renderer, committed: decision.action === "render" };12}13 14console.log(navigate(typed));The browser checks the reply
The server's reply is called a response. It has three parts:
- A status: a number such as 200 (OK) or 404 (not found).
- Headers: short notes about the reply.
- The bytes: the data itself.
One header, Content-Type, says what kind of data the server meant to send. For a web page it is text/html. In Chrome, the network part reads this header. When needed, it also looks at the first few bytes. Then it decides: is this a page for a renderer to show, or a file to download?
1function decide(status, contentType, location) {2 if (status >= 300 && status < 400 && location) return "follow redirect";3 if (status === 204) return "stay on current page";4 if (contentType.includes("text/html")) return status >= 400 ? "render error page" : "render";5 return "download";6}7 8console.log(decide(200, "text/html"));The function checks the reply in order:
- Line 2 handles a redirect: a status from 300 to 399, plus a
location, the new address. - Line 3 handles 204, which means "no content". In this model there is no new page to show, so the browser stays on the current page.
- Line 4 handles HTML. That includes an HTML 404 page, which is the server's own "not found" page.
- Line 5 sends any other data, such as a zip file, to be downloaded.
Line 8 prints render, because a 200 HTML reply is a page to show.
decide() function checks the reply in this order. Only HTML replies become a page to show.The table below lists five kinds of response. Read each row across: what the server sends, what it means, and what the lesson model does with it.
| What the server sends | What it means | What the model does |
|---|---|---|
200 + text/html | A web page. | Pick a renderer and show the page. |
| 204 | No content: there is no new page to show. | Stay on the current page. |
| 301 + a location | A redirect: the page is at another address. | Start another request for that address. |
404 + text/html | The server's own HTML "not found" page. | Show that error page. |
200 + application/zip | A file that is not a web page. | Send it to be downloaded. |
The playground below lets you pick one response at a time. It is much smaller than a real browser. It leaves out many real checks, such as extra redirect rules, security rules, guessing the file type from its bytes, and permissions. It teaches the first useful question: which responses can become the next page?
1function decide(status, contentType, location) {2 if (status >= 300 && status < 400 && location) return "follow redirect";3 if (status === 204) return "stay on current page";4 if (contentType.includes("text/html")) return status >= 400 ? "render error page" : "render";5 return "download";6}7 8console.log(decide(200, "text/html"));200The status number of the reply you picked.
text/htmlThe model uses this to choose: show a page, or download a file.
renderHTML document
200 text/html: An HTML page goes to a renderer, which shows it as the new page.
The browser picks a renderer
A renderer process is the part of the browser that loads a page's HTML and later runs the page's JavaScript. In Chrome, the network thread tells the UI thread when the response is ready to show. Then the UI thread finds a renderer process to show the page.
1const decision = "render";2const renderer = decision === "render" ? "renderer ready" : "not needed";3console.log(renderer);4console.log(decision === "render");Line 1 stores the decision from the response step. Line 2 turns it into an easy-to-read state. Line 3 prints renderer ready, and line 4 prints true. This is only a model. Web pages have no function for choosing a renderer. Page code cannot pick a Chrome renderer process.
Chrome can save time here. When the UI thread starts the request, it already knows which site the user is probably going to. So it can find or start a renderer while the network work is still going on. If a redirect sends the user to another site, that early renderer may not be used. Most of the time it is used, and starting early saves time.
Chrome chooses processes to keep sites safe from each other and to keep the browser stable. Giving each site its own renderer is called site isolation. A page can read web features such as location, history, and performance entries. It cannot ask for a particular renderer process.
The new page commits
Commit is the clear line between getting a page ready and making it the active page. In Chrome, once the response and a renderer are both ready, the browser process sends the renderer a message: "commit this page". A message between two processes is called an IPC, short for inter-process communication. The browser also hands over the stream of data, so the renderer can keep receiving HTML.
1const oldPage = "cart";2const newPage = "checkout";3 4function commitNavigation(oldDocument, newDocument) {5 const addressBar = "/checkout";6 const history = ["/cart", addressBar];7 const oldPageState = "may enter bfcache";8 return { addressBar, history, oldPageState, document: newDocument };9}10 11console.log(commitNavigation(oldPage, newPage).addressBar);- Lines 1 and 2 name the old page and the new page.
- Line 5 gives the address bar its new address.
- Line 6 adds that address to the session history. That is the tab's list of visited pages, which the Back button uses.
- Line 7 notes what may happen to the old page. It is a maybe, not a promise.
- Line 11 prints
/checkout.
This model shows the changes you can see. It is not how a browser stores history inside.
/cart to /checkout. The new address is added to the history list, so Back can return to the cart.The table below follows one navigation in time. Read each row from top to bottom: what is ready at that moment, and what changes.
| When | What is ready | What changes |
|---|---|---|
| Navigation starts | A request has started. | Nothing yet. The old page is still the active page. |
| The response is ready | The browser's checks say the reply is a page. | A renderer is picked or started. |
| Commit | The browser process tells the renderer to commit. | The address bar and the session history change. |
| The new page loads | The renderer receives the HTML and the files it needs. | Reading the HTML, running scripts, and drawing go on after commit. |
Step through a small teaching model of commit. It shows the changes you can see. Page JavaScript cannot see Chrome's private messages.
script
2const newPage = "checkout";3 4function commitNavigation(oldDocument, newDocument) {5 const addressBar = "/checkout";6 const history = ["/cart", addressBar];7 const oldPageState = "may enter bfcache";8 return { addressBar, history, oldPageState, document: newDocument };9}10 11console.log(commitNavigation(oldPage, newPage).addressBar);When you leave a page, the browser may ask the old page about beforeunload. That event lets a page ask "Leave this page?". The browser can also keep the old page in the back/forward cache, or bfcache. The bfcache keeps a whole page in memory, so Back can show it again at once. When that happens, the unload event does not run.
So don't use unload as a last chance to save data. The Page lifecycle lesson covers these events in detail.
The new page keeps loading
Commit does not mean everything has loaded. It means the renderer has taken the new document as the active page. After commit, the renderer can still:
- receive more HTML,
- read the HTML and build the page,
- ask for more files, such as images and scripts,
- run scripts.
Page JavaScript can also keep fetching data and changing the page after the browser's loading spinner stops.
1const entry = performance.getEntriesByType("navigation")[0];2console.log(entry ? entry.type : "no navigation entry here");Line 1 asks the Performance API for the first navigation entry. The Performance API is a browser feature that records timing facts about the page. Line 2 prints the entry's type, such as navigate. If there is no entry, it prints no navigation entry here. That check keeps the code safe in places with no entry, such as some code sandboxes.
This entry is good for measuring timing that the page can see. It does not show the browser's private messages. Treat it as a fact about this page's navigation, not as a view of how one browser plans its work inside. The next lesson starts from here: how the received HTML finds and runs scripts.
Not every navigation follows this full path:
- The Navigation API can catch a move to another address on the same origin (the same protocol, site name, and port). It can change the page without loading a new document.
- Back can bring a page back from the bfcache.
- Some browsers can show a page that was prerendered, which means loaded early in the background. Speculation rules tell the browser which pages to prerender.
None of these fetch and read a fresh page at the moment it is shown.
A service worker can answer instead
A service worker is JavaScript from the website that runs apart from the page. It sits between the page and the network and can answer requests. The addresses it looks after are called its scope.
When you navigate to an address in its scope, the browser can start the worker and send it a fetch event. The worker may answer with:
- a saved copy from its cache,
- a fresh reply from the network,
- or a reply it builds itself.
1async function responseFor(cached, network) {2 if (cached) return "cache";3 await network;4 return "network";5}6 7responseFor(true, Promise.resolve()).then(console.log);Line 1 takes two things: cached, a saved copy, and network, a promise for the network reply. Line 2 returns cache at once when a saved copy exists. Lines 3 and 4 wait for the network only when there is no saved copy. The last line prints cache.
A real service worker returns Response objects. This model uses short strings so the choice is easy to see.
In Chrome, the network thread checks which service workers are registered, and for which addresses. If the address matches, the browser runs the service worker's code in a renderer process. When preload is off, a reply from the cache can skip the network request. But a network-first worker, one that asks the network before its cache, has to start up before it begins fetching. That start-up time adds a delay.
Navigation preload saves waiting time
Navigation preload helps when a service worker will need the network anyway. Without it, the browser first waits for a stopped worker to start. Only then does the worker begin its network fetch.
With preload on, the browser starts the network request while the worker is starting. So the two waits happen at the same time. This works for GET navigation requests, the normal kind of request when you open a page.
1addEventListener("activate", (event) => {2 event.waitUntil((async () => {3 if (self.registration.navigationPreload) {4 await self.registration.navigationPreload.enable();5 }6 })());7});8 9addEventListener("fetch", (event) => {10 event.respondWith((async () => {11 const cached = await caches.match(event.request);12 if (cached) return cached;13 const preloaded = await event.preloadResponse;14 return preloaded || fetch(event.request);15 })());16});The activate event runs when a new service worker takes over:
- Line 2 tells the browser to wait until the setup work inside it is done.
- Line 3 checks that this browser supports navigation preload.
- Line 4 turns preload on.
The fetch event runs for each request. Line 10 says this handler will give the response. Lines 11 and 12 answer from the cache when there is a saved copy. Line 13 waits for event.preloadResponse, the response that preload fetched. Line 14 uses it if it exists. If not, it does a normal fetch. This code only runs inside a service worker, so you can read it here but not run it on this page.
The key step is using the preload response. If you turn preload on but always call fetch(event.request), the browser can make two requests for the same page.
When preload is on, event.preloadResponse gives a Response for navigation requests that qualify. Otherwise it gives undefined. That is why the normal fetch stays in the code as a backup. Also, with preload on, the browser may already have started a network request, even when this handler answers from the cache.
You tap Order in a food app. The restaurant starts cooking right away. At the same time, a helper wakes up to check your coupons. The kitchen does not wait for the helper before it starts. Doing both at once saves time.
- In real life: You tap Order
- In JavaScript: A navigation request begins
- In real life: The restaurant starts cooking
- In JavaScript: Preload starts the network request
- In real life: A helper wakes up to check coupons
- In JavaScript: The service worker starts
- In real life: The helper hands over the ready food
- In JavaScript: The fetch handler uses
event.preloadResponse
Use these facts in real apps
These steps explain several things you will meet in real apps:
- A redirect means the browser starts another request before it shows anything.
- A page can be used right after commit, but its images and scripts may still be loading.
- A service worker cache can make a navigation fast.
- A network-first service worker should think about preload when its start-up slows the request.
Some good habits follow from them:
- Save important draft data while the user types. Don't wait for the user to leave the page.
- Use the Performance API to measure navigation timing the page can see. Don't guess at the browser's private timings.
- Treat redirects and error pages as normal results in your app.
- Choose on purpose what your service worker caches. If you turn preload on, use
event.preloadResponse. - Keep loading messages honest: commit does not mean every file has finished loading.
- Deciding if address-bar text is a search or an address
- Starting the network request for the page
- Sending the message that says "commit this page"
- Receiving the HTML and loading the page
- Running the new page's JavaScript
- Choosing a cached or preloaded reply for the page
Sort each card by the part of Chrome that does this job, as Chrome describes it.
Knowing who does each job helps you find bugs. Faster painting can't fix a slow server redirect. A CSS change can't make a stopped service worker start sooner. First find which step is slow. Then choose a fix.
Common misconceptions
- “Pressing Enter replaces the page at once.” First come the request, the response checks, the renderer choice, and commit.
- “Commit means everything has loaded.” Commit makes the new page active. Reading the HTML and loading images and scripts go on after it.
- “A redirect is an error page.” A redirect asks the browser to request another address before any page commits.
- “A service worker always makes navigation faster.” A network-first worker that must start up first can add a delay, unless preload is on.
- “Navigation preload replaces the fetch handler.” The handler must still use
event.preloadResponseor choose another response.
The four words below name different moments. Read each row across: what the word means, and what it does not mean.
| Word | What it means | What it does not mean |
|---|---|---|
| Request | The browser asks a server or a service worker for something. | It does not mean the new page is already active. |
| Response | The reply: a status, headers, and bytes. | It does not have to be HTML. |
| Commit | A renderer takes the next page as the active page. | It does not mean every file has finished loading. |
| Navigation preload | The network request starts while the service worker starts. | It does not mean the worker can skip event.preloadResponse. |
Practice exercises
Read the small URL program. Then type the two values it prints.
const url = new URL("https://shop.example/cart?item=tea");
console.log(url.hostname, url.searchParams.get("item"));It prints shop.example tea. The URL object splits the address: hostname is just the site name, and item is the value after item=.
Which kind does the lesson model give how to make tea: search or url?
The answer is search. how to make tea has no dot and doesn't start with http, so the model calls it a search.
Predict what it prints. Then say what a real browser does next.
function decide(status, location) {
return status === 301 && location ? "follow redirect" : "render";
}
console.log(decide(301, "/new-cart"));It prints follow redirect. A redirect leads to another request. It does not show a page yet.
A request gets a 204 reply. What should the response playground's model do?
The model stays on the current page. That is the lesson model's choice for a reply with no content. It is not a full list of every browser rule.
A network-first service worker asks the network before its cache. Why might it turn on navigation preload?
Navigation preload starts the network request while the service worker is starting. Later, the fetch handler can use event.preloadResponse when it is ready.
Your checkout form has a draft address. What should the app save before a user leaves?
Save important user data while the user edits. Events about leaving the page are useful hints, but important drafts should not depend only on them.
Check your understanding
For each question, first find its step: input, request, response, renderer, commit, then loading or the service worker.
Question 1 of 7In Chrome, which part reads the text you type in the address bar first?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictPop out in the code editor (opens in a new tab)JavaScriptconst url = new URL("https://shop.example/cart?item=tea"); console.log(url.hostname, url.searchParams.get("item"));Choose an answer to see the explanation.
Question 3 of 7A navigation gets a 301 reply with a Location header (the new address). What happens in this lesson's model?
Choose an answer to see the explanation.
Question 4 of 7Why can Chrome get a renderer ready before the reply arrives?
Choose an answer to see the explanation.
Question 5 of 7What happens at commit, as Chrome describes it?
Choose an answer to see the explanation.
Question 6 of 7A service worker needs the network. What wait does navigation preload save?
Choose an answer to see the explanation.
Question 7 of 7What does this code print when there is no navigation entry?
Read the code, then predictPop out in the code editor (opens in a new tab)JavaScriptconst entry = undefined; console.log(entry ? entry.type : "no navigation entry here");Choose an answer to see the explanation.
Key takeaways
- In Chrome, the browser process reads address-bar text first. It decides: search or address?
- A response can lead to five results: show the page, follow a redirect, stay on the current page, show an error page, or download a file.
- Chrome can get a renderer ready while the request is still going. When both are ready, it sends the renderer a commit message (an IPC).
- Commit makes the new page active, and the address bar and history change. Loading goes on after commit.
- A service worker can answer a navigation. Navigation preload starts the network request while the worker starts.
- Measure what the page can see with browser features. Use the steps of a navigation to find which part to debug.
Remember the one-liner.
A navigation becomes the new page only when a suitable response reaches a renderer, and that renderer commits it.
Coming next: From HTML to running script. There, the renderer reads the new page's HTML, fetches its files, and runs its JavaScript.