Week 3 · The web frontend: SPAs, browser storage and tooling
The one-minute story
A paper form versus a whiteboard. An old-style website is a stack of paper forms: to change anything you hand the form back to the clerk (the server), who prints a whole new page and gives it to you. A single-page application (SPA) is a whiteboard on your desk: the clerk gives you the board once, and from then on you rub out and rewrite only the part that changed. You only call the clerk when you need fresh facts (an API call, with fetch from week 2).
But a whiteboard needs care. If your notes are only on the board (in the page's memory) and someone refreshes the page, they vanish. So you decide where each fact lives: on the clerk's shelf (server database), in your pocket (browser storage), on the whiteboard (memory), or written on the address (the URL). And keeping the board in sync with your notes by hand gets painful, which is why frameworks such as Vue exist. Lastly there is the toolbox (npm, bundlers, dev server) that builds and runs all this.
By the end of this week you can:
- compare multi-page and single-page apps and explain client-side routing;
- choose between cookies,
localStorage,sessionStorageand IndexedDB and predict storage code; - explain what a service worker and a PWA are;
- explain why frameworks and the virtual DOM exist, and what
package.json,node_modules, Vite or webpack and Babel do; - say where the state of a web application lives, and why HTTP being stateless matters.
Words you need
- MPA (multi-page app) - every click asks the server for a new full HTML page.
- SPA (single-page app) - one HTML page is loaded once; JavaScript redraws parts and fetches data.
- Client-side routing - changing the URL and the view in the browser without asking the server for a new page.
- Origin - scheme + host + port, such as
http://localhost:8080. Storage is separated per origin. - Cookie - a tiny named value the server (or script) stores in the browser; it travels with every request to that site.
- Web Storage -
localStorageandsessionStorage: key-value text in the browser. - IndexedDB - a bigger, asynchronous database in the browser for objects and files.
- Service worker - a background script that sits between page and network and can serve cached files offline.
- PWA - a web app made installable and offline-capable with a manifest and a service worker.
- Virtual DOM - a light copy of the page in memory used to work out the smallest real change.
- npm - Node's package manager; reads
package.jsonand fillsnode_modules. - Bundler - a tool (webpack, Vite) that combines your many files into a few optimized ones.
- Transpiling - converting new JavaScript into older JavaScript old browsers understand (Babel).
- Hot reload - the dev server updates the page when you save, without a full refresh.
- Stateless - the server does not remember you between requests by itself.
Idea 1: Multi-page apps, single-page apps and routing
| MPA (traditional) | SPA | |
|---|---|---|
| Click on a link | full page request, white flash | JavaScript swaps the view, no reload |
| What the server sends | finished HTML for each page | one HTML shell + JS once, then JSON from APIs |
| Speed after first load | slower (whole page again) | fast, smooth, app-like |
| First load | quick (just HTML) | slower (download the JS bundle first) |
| SEO / search engines | easy: the HTML already has the content | harder: content is built by JS (fixed by server-side rendering) |
| Server load | higher (renders every page) | lower (only data) |
| Example | a classic shopping site, a Flask app with Jinja templates | Gmail-style apps, your Vue project |
Client-side routing: the SPA watches the URL and shows the matching component, without a request for a new page. Vue Router 3 has two modes:
- hash mode (default):
http://localhost:8080/#/records/2. The part after#never goes to the server, so the server needs no special setup. - history mode:
http://localhost:8080/records/2, clean URLs, but the server must send the sameindex.htmlfor every path, or a refresh gives a 404.
This is why exam questions give both .../records/1 and .../#/records/1 and ask which one renders: in hash mode the router only reads what is after #. In history mode /#/inventory/102 does not match the route /inventory/:id, because the path is then just /.
A SPA still talks to a backend: your Flask API returns JSON and the Vue code draws it (week 2's fetch).
- SPAs do not load the whole page on every click; that is the MPA behaviour.
- "The idea of an SPA is to improve SEO" is false. SEO is normally the SPA's weak spot.
- gmail.com is SPA-like; amazon.in is a classic multi-page site. "Both are SPAs" is false.
- Papers disagree on one claim: "client-side routing gives better SEO by default" is marked correct in one End Term and wrong in another. Safe, agreed benefits: faster navigation, no full reload, less load on the backend.
Which of these describe an SPA? (a) The server returns finished HTML for every click. (b) After the first load only JSON travels. (c) The URL can change without a page reload. (d) It works best for search-engine visibility without extra work.
Need a hint?
Think about what the server sends.
Show the full solution
(b) and (c). (a) is MPA behaviour. (d) is not an SPA strength; it needs server-side rendering or similar help.
Idea 2: Browser storage
A web page can keep data in the browser in four main ways. Know their differences cold; the papers ask matching and "which statement is true" questions.
| Cookie | localStorage | sessionStorage | IndexedDB | |
|---|---|---|---|---|
| Size (about) | about 4 KB each | about 5 MB per origin | about 5 MB per origin | very large (a share of the disk) |
| Lifetime | until its expiry date (or end of session if none) | forever until cleared by code or user | until the tab is closed (survives reload) | until cleared |
| Sent to the server | yes, on every request to that site | no | no | no |
| Shared across tabs | yes (same site) | yes (same origin) | no, each tab has its own | yes (same origin) |
| Who can read | the server, and JS (unless HttpOnly) | JS of the same origin | JS of that tab, same origin | JS of the same origin |
| Data type | small text | text only (strings) | text only (strings) | objects, blobs, files, audio, video |
| API | document.cookie, Set-Cookie header | getItem, setItem, removeItem, clear | the same four | asynchronous, with requests or promises |
All storage is per origin and per browser: nothing is shared across devices or across different websites. Storage is not secure: any script on the page can read it, so never keep passwords there (tokens are stored there in practice, with that risk).
// browser
localStorage.setItem("theme", "dark"); // value becomes the string "dark"
localStorage.getItem("theme"); // "dark"
localStorage.getItem("missing"); // null (getItem returns null if absent)
localStorage.missing; // undefined (property style returns undefined)
localStorage.setItem("n", 5); // stored as "5"
typeof localStorage.getItem("n"); // "string"
localStorage.setItem("user", JSON.stringify({ id: 1 }));
JSON.parse(localStorage.getItem("user")).id; // 1 objects need JSON both ways
localStorage.removeItem("theme"); localStorage.clear();
Everything in Web Storage is a string. setItem("n", 5) stores "5"; getItem("n") + 1 is "51"; getItem("n") * 2 is 10. Objects need JSON.stringify to store and JSON.parse to read.
// browser
localStorage.setItem('counter', '0');
sessionStorage.setItem('total', '5');
for (let i = 0; i < 3; i++) {
let counter = localStorage.getItem('counter');
let total = sessionStorage.getItem('total');
counter += 2;
total *= 2;
localStorage.setItem('counter', counter);
sessionStorage.setItem('total', total);
}
sessionStorage.clear();
console.log(localStorage.getItem('counter'));
console.log(sessionStorage.getItem('total'));
counter is the string "0", so counter += 2 joins: "02", then "022", then "0222". total is "5", and *= converts to a number: 10, 20, 40. Then sessionStorage.clear() removes everything, so the last line is null (not undefined: getItem returns null). Printed: 0222 and null.
A sibling question starts with score = '10' and multiplier = '3', runs the loop twice with score + 5 and multiplier * 2: "105", "1055" and 6, 12. The printed 1055 and 12 appeared as "None of these" among the options.
A very common long question: a Vue-style component whose mounted() reads storage, then writes it back changed. The skill is to trace the storage after each refresh.
// browser: runs on every page load
let name, age;
try {
const parts = localStorage.getItem("name").split(" "); // fails with TypeError on null
name = parts[0];
age = parts[1];
localStorage.setItem("name", parts[0] + " " + name); // writes back
} catch {
name = "Default"; age = "Default";
}
Suppose the user types IIT Madras and a button saves it to localStorage.name.
- First ever load, storage empty:
getItemreturnsnull,.splitthrows, thecatchsetsDefault, Default. - Refresh 1: reads
"IIT Madras".name = "IIT",age = "Madras". Shows IIT, Madras. Writes back"IIT" + " " + "IIT"="IIT IIT". - Refresh 2: reads
"IIT IIT". Shows IIT, IIT. Writes"IIT IIT"again. It stays that way forever.
Always ask: what was in storage before this load, what did the code show, and what did it write after?
localStorage.getItem(missing)isnull;localStorage.missingisundefined.- Values come back as strings:
"3" + 2is"32". sessionStorageis per tab: a fresh new tab does not see it;localStorageis shared by all tabs of the origin and survives closing the browser.- A reload does not clear
sessionStorage; closing the tab does. - Storage does not travel between devices and is not sent to the server (unlike cookies).
Which storage fits: (a) the user's dark-mode choice, kept after the browser restarts; (b) a half-filled form that should vanish when the tab closes; (c) a login cookie the server must receive on every request; (d) 200 MB of cached video for offline use.
Need a hint?
Think about lifetime and size.
Show the full solution
(a) localStorage. (b) sessionStorage. (c) a cookie (the only one sent to the server automatically). (d) IndexedDB (the only one built for large data and files).
// browser
sessionStorage.setItem("username", "course_user");
const a = sessionStorage.getItem("username");
sessionStorage.removeItem("username");
const b = sessionStorage.getItem("username");
sessionStorage.clear();
const c = sessionStorage.username;
What are a, b, c?
Need a hint?
getItem and property access differ for missing keys.
Show the full solution
a is 'course_user', b is null, c is undefined.
Idea 3: Statelessness and where state lives
HTTP is stateless: each request stands alone, and the server does not automatically remember the previous one. Yet apps remember you are logged in and what is in your cart. The state is kept somewhere and explicitly sent along with requests.
| Where | Examples | Survives reload? | Shared with others? |
|---|---|---|---|
| Server (database, cache) | users, products, orders | yes | yes, the source of truth |
Client memory (JS variables, Vue data) | which tab is open, loading flag, text typed so far | no | no |
| Browser storage | theme, token, saved draft | yes | no (this browser only) |
URL (path, ?query, #) | current page, search text, page number | yes, and bookmarkable | yes, by sharing the link |
| Cookie / token | session id, login token | yes | sent with each request |
- UI state (ephemeral state): short-lived interface facts such as a loading spinner, which tab is selected, a dropdown open or closed. It lives in the component and can be lost.
- Application state: real data (users, products), usually owned by the server, and cached in the frontend (week 4's Vuex).
Rule of thumb. Truth lives on the server. Convenience copies live in the client. What should survive a reload, or be shared by link, goes in storage or in the URL. Because HTTP is stateless, the client or server holds the state and sends it in each request (cookie, token, query string).
Where would you keep: (a) the list of all students; (b) whether a modal is open; (c) the page number of a table you want to bookmark; (d) the login token?
Need a hint?
Ask: who needs it, and must it survive reload?
Show the full solution
(a) the server (database), fetched through the API. (b) component memory (UI state). (c) the URL query (?page=3). (d) browser storage (or a cookie), sent with each request.
Idea 4: Why frameworks exist
With plain JavaScript you change the page by hand:
// browser: the manual way
let count = 0;
document.querySelector("#plus").addEventListener("click", () => {
count++;
document.querySelector("#out").textContent = count; // you must remember to update the page
});
With ten pieces of data shown in five places, you will forget one, and the page and the data drift apart. A framework makes the page a function of the state: change the data and the framework updates the page.
<div id="app">{{ count }} <button @click="count++">+</button></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
new Vue({ el: "#app", data: { count: 0 } }); // Vue 2: click changes data, page follows
</script>
- Virtual DOM. Touching the real DOM is slow. The framework keeps a light JavaScript copy of the page. When data changes it builds a new virtual tree, compares it to the old one (the "diff") and patches only the real nodes that differ. So Vue "detects the change and updates efficiently"; it does not reload the page or rebuild everything.
- Components. The page is built from reusable pieces (a navbar, a card), each with its own template, data and methods. Advantages: reusability, maintainability, easier UI management.
- Reactivity (Vue 2). Vue wraps your
dataproperties so it knows when they change. A limit: a property added later, orarr[0] = x, is not noticed; usethis.$set(...)or array methods likepush. (Vue 3 uses Proxies and has no such limit.)
| Vue | React | Angular | |
|---|---|---|---|
| Kind | progressive framework | UI library | full framework |
| Made by | Evan You and community | Meta | |
| Templates | HTML-like templates, optional single-file components | JSX (HTML inside JS) | HTML templates + TypeScript |
| Learning curve | gentle; can be added with one script tag | moderate | steep, many built-in parts |
The course uses Vue 2 with Vue Router 3 and Vuex 3. In newer Vue (3) the same ideas apply, with Pinia replacing Vuex.
Idea 5: Tooling, npm, bundlers and dev servers
Two ways to start with Vue:
| CDN script tag | Vue CLI / build tools | |
|---|---|---|
| Setup | one script tag, no install | npm + a generated project |
| Templates | compiled in the browser | compiled at build time (faster) |
.vue single-file components | no | yes |
| Modern syntax, minify, tree-shaking | no | yes (via Babel and the bundler) |
| Best for | demos, small pages, learning | real projects, teams |
npm and package.json. npm downloads packages. The project's package.json lists them. npm install reads it and fills the node_modules folder (huge; never commit it, it is rebuilt from package.json and package-lock.json).
{
"name": "my-vue-app",
"version": "0.1.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": { "vue": "^2.6.14" },
"devDependencies": { "@vue/cli-service": "^5.0.0", "babel-eslint": "^10.1.0" }
}
dependenciesare needed by the running app;devDependenciesonly while developing and building.^2.6.14means "2.6.14 or any newer 2.x" (same major version);~2.6.14allows only newer patches.scriptsare run withnpm run name.npm run servestarts the dev server (usuallylocalhost:8080) with hot reload.npm run buildmakes the optimized production files in thedistfolder and does not start a server.npm install vue@2adds a dependency;npm install -D pkgadds a dev dependency;npxruns a tool without installing it globally.
A developer creates a Vue 2 project and runs npm run build hoping to see the app in the browser. What happens?
build compiles and bundles the app for production into dist/. It starts nothing. She should run npm run serve for the local dev server with hot reload. The same two scripts exist in every Vue CLI project because package.json defines them.
What the build tools do.
- Bundler (webpack inside Vue CLI; Vite in the newer Vue starter): follows your
imports, joins many files into a few, minifies them, removes unused code (tree-shaking) and handles.vuefiles, CSS and images. Webpack bundles first and is configuration-heavy; Vite serves your modules directly during development, so it starts and updates very fast. - Transpiling (Babel): rewrites new syntax (arrow functions,
?., classes) into older JavaScript so old browsers can run it. - Dev server + hot reload (HMR): save a file and the changed part is swapped into the open page, often keeping your current state.
npm run builddoes not start the app;npm run servedoes (in Vue CLI projects).node_modulesis not committed and not edited by hand.- Vue can be installed through npm (a False-statement question said "it cannot be").
- A CDN script is fine for a demo; a real project uses the build tools.
Idea 6: Service workers, PWAs, responsive design and accessibility
Web worker: runs JavaScript on a separate thread so heavy work does not freeze the page. It cannot touch the DOM; it talks to the page with postMessage.
Service worker: a special worker that sits between your page and the network. It can intercept requests, answer from a cache, work offline, and receive push notifications. It runs only on HTTPS (and localhost for development).
// browser
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js"); // sw.js decides what to cache and serve
}
PWA (Progressive Web App): an ordinary web app plus
- a web app manifest (a JSON file: name, icons, start URL, display mode), which lets it be installed on the home screen and look like a native app;
- a service worker, giving offline support and fast loads;
- served over HTTPS.
It needs no app store and no native code, and it can work with a poor or no connection.
Responsive design: one page that adapts to any screen size. Tools: the viewport tag, CSS media queries, flexbox and grid, relative units.
<meta name="viewport" content="width=device-width, initial-scale=1">
.card { width: 30%; }
@media (max-width: 600px) { .card { width: 100%; } } /* on a phone, cards go full width */
Accessibility reminder: use real elements (button, label, headings), give images alt text, make everything usable with a keyboard, keep good colour contrast, and let screen readers understand the page (ARIA only when plain HTML is not enough).
(a) A PWA must be downloaded from an app store. (b) A service worker lets a PWA show cached pages offline. (c) PWAs can be added to the home screen. (d) A web worker can change the DOM directly.
Need a hint?
Recall what each piece does.
Show the full solution
(a) False. (b) True. (c) True. (d) False; workers have no DOM access.
Idea 7: Debugging the frontend with dev tools
Press F12. The tabs you use:
| Tab | Use |
|---|---|
| Elements | see and edit the live DOM and CSS |
| Console | read errors and console.log, run JavaScript |
| Network | see every request: URL, method, status code, request and response, timing (check your fetch here) |
| Application | inspect and edit cookies, localStorage, sessionStorage, IndexedDB, service workers and the manifest |
| Sources | set breakpoints and step through code |
| Device toolbar | test a phone-sized screen (responsive design) |
The Vue devtools extension adds a tab showing components, their data, and Vuex state. Debug order: Console for the error message, Network for what the server answered (status, response body), Application for what is stored.
Be ready to demo this in the viva: open the Network tab and show your API call and its status code; open Application and show the token in storage; change a store value and show the page update. Also be ready for "why is your project an SPA, and what happens on refresh?".
Exam radar
The keyword scan finds about 32 distinct questions for this week (about 7 in Quiz 1, 57 in Quiz 2 and 39 in the End Term by keyword, so approximate). Most are storage questions; they are also the easiest to get right:
- Predict the storage output (string joining with
+,nullafterclear), and "refresh the page N times" traces. - True/false on Web Storage (
localStoragenever expires;sessionStorageis per tab, not shared across tabs). - SPA/PWA statements (client-side routing, service workers, home screen, offline).
- Client-side storage advantages (preferences, offline documents, IndexedDB for audio/video; not "data stored across devices").
- Tooling:
npm run buildversusnpm run serve; "Vue cannot be used through npm" (false); component advantages; virtual DOM. - State: UI (ephemeral) state; HTTP is stateless so state is exchanged in requests.
- A new tab:
localStorageyes,sessionStorageno. getItemof a missing key isnull.- Stored numbers come back as strings.
- Client-side storage never syncs across devices.
Quick quiz
Tab 1 runs localStorage.setItem("theme","dark"); sessionStorage.setItem("cart","3-items");. A fresh Tab 2 on the same site then reads both. What do they return?
- both values
- both null
- "dark" for theme, null for cart
- null for theme, "3-items" for cart
localStorage is shared by all tabs of an origin; sessionStorage belongs to one tab.localStorage.setItem('c','0'); let c = localStorage.getItem('c'); c += 2; console.log(c) prints...
- 2
- 02
- 0
- NaN
+= joins text.Which are advantages of client-side storage? (Select all) (select all that apply)
- Storing personalised site preferences
- IndexedDB can hold large files such as audio and video
- Saving generated documents for offline use
- The data is shared across all your devices
Select all TRUE statements about SPAs and PWAs. (select all that apply)
- SPAs often use client-side routing to update content without full reloads.
- PWAs use service workers for offline use.
- PWAs always need an internet connection.
- SPAs load a whole new page from the server on each interaction.
In a Vue CLI project, which command makes the production files in dist without starting a server?
npm run servenpm run buildnpm start devnpm install
serve starts the dev server; build compiles for production.In Vue 2, what happens when you change a data property?
- The page reloads
- Nothing until you refresh
- The virtual DOM finds the difference and updates only what changed
- The whole page is rebuilt from scratch
Viva: say it out loud
What is a single-page application and why did you choose one?
An SPA loads one HTML page and JavaScript once, then changes the view in the browser and gets only data (JSON) from the API. It gives fast, app-like navigation and puts less load on the server, and it fits a Vue frontend over a Flask API.
What is client-side routing?
The browser, not the server, decides which view to show for a URL. Vue Router changes the component when the URL changes, in hash mode (after #) or history mode, so there is no full page reload.
Where is the login token stored in your project, and what are the risks?
It is kept in localStorage (or a cookie) and sent in the Authorization header with each request. Any script on the page can read localStorage, so XSS is a risk, and the token is lost if the user clears the browser data.
What is the difference between localStorage and sessionStorage?
localStorage stays until cleared and is shared across tabs of the same origin. sessionStorage lives only as long as that one tab and is not shared. Both hold strings and are about 5 MB.
What does the virtual DOM do?
It is a lightweight copy of the page in memory. When the data changes, the framework builds a new copy, compares it with the old one, and applies only the real differences to the page.
What are package.json and node_modules?
package.json lists the project's name, scripts and dependencies. npm install downloads those packages into node_modules, which is large and not committed.
What is a PWA?
A web app with a manifest and a service worker (over HTTPS). It can be installed on the home screen and work offline, without an app store.
Recap
- MPA: new full page per click. SPA: one shell, JavaScript redraws, JSON from APIs. SPA is fast and light on the server, weaker for SEO.
- Client-side routing: hash mode (
/#/path) needs no server setup; history mode needs a fallback toindex.html. - Cookies (4 KB, sent to server),
localStorage(persistent, shared by tabs),sessionStorage(per tab), IndexedDB (large, async, files). - Web Storage holds strings:
"0" + 2is"02"; missing key givesnull. - HTTP is stateless; state lives on the server, in memory, in storage or in the URL, and is sent explicitly.
- Frameworks keep UI in sync with state; the virtual DOM patches only differences; components give reuse.
package.json+npm installfillnode_modules;npm run servefor development,npm run buildfordist/; bundlers (webpack, Vite) and Babel prepare the code.- PWA = manifest + service worker + HTTPS. Responsive = viewport + media queries. Use dev tools (Network, Application).
localStorage.setItem(k, v),getItem(k)(null if missing),removeItem(k),clear();sessionStoragehas the same APIJSON.stringify(obj)before storing,JSON.parse(str)after reading- Sizes: cookie about 4 KB; web storage about 5 MB per origin
npm install,npm run serve(dev, hot reload),npm run build(todist/)^2.6.14= same major, newer minor and patch allowed<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>;new Vue({ el: "#app", data: {} })navigator.serviceWorker.register("/sw.js"); manifest = name, icons, start_url, display<meta name="viewport" content="width=device-width, initial-scale=1">
- MAD1 week 1 - How the web works: clients, servers, URLs and HTTP: A single-page app is still a browser talking HTTP to a server: the request/response model from MAD1 week 1.
- MAD1 week 11 - HTML5, accessibility, encoding and beyond HTML: HTML5 storage, accessibility and PWAs are the platform features that modern frontends build on.
Practise with real exam questions
These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week All Quiz 1 papers