In 2026, making your website visible and navigable to AI agents is essential because modern users no longer just search the web; they deploy autonomous AI assistants to discover, compare, and summarize services for them. If your application cannot be easily parsed by AI crawlers, it effectively becomes invisible to the next generation of web traffic.
To achieve this discoverability safely, you must strictly separate your public content from your protected data. Ensure that public-facing pages use Server-Side Rendering (SSR) to deliver fully formed HTML, leverage standard
Security remains completely uncompromised during this process though because AI agents interact with your platform exactly like anonymous, logged-out visitors. While bots crawl your public semantic structures, your backend continues to shield private user data behind secure, server-side
By explicitly structuring your public routes for machine readability while leaving your authentication walls intact, you can safely maximize your application's reach in the AI-driven ecosystem.
To achieve this discoverability safely, you must strictly separate your public content from your protected data. Ensure that public-facing pages use Server-Side Rendering (SSR) to deliver fully formed HTML, leverage standard
robots.txt files to guide permitted bots, and implement Schema.org JSON-LD structured data so AI agents can instantly comprehend your app's core purpose without guessing.Security remains completely uncompromised during this process though because AI agents interact with your platform exactly like anonymous, logged-out visitors. While bots crawl your public semantic structures, your backend continues to shield private user data behind secure, server-side
HttpOnly JWT cookies. Any attempt by an AI crawler to access private endpoints - such as user dashboards or checkout screens - will be instantly blocked with a 401 Unauthorized response since the bot lacks the necessary session cookies. By explicitly structuring your public routes for machine readability while leaving your authentication walls intact, you can safely maximize your application's reach in the AI-driven ecosystem.
What are we planning for the summer?
Anonymous Poll
25%
Frontend
18%
Backend
61%
Fullstack
25%
AI/ML
7%
UI/UX
11%
Web3
14%
Haven't figured out yet
🌟 Week 9 Day 5 --- Redux & Zustand
Good Evening campers 💙
Part 1 --- Redux Toolkit (RTK)
Redux has been the standard for large-scale React applications for nearly a decade, centralizing application state in a single object called the "store." Instead of mutating this object directly, you dispatch "actions" that describe changes, and pure "reducers" compute the next state based on these actions. This unidirectional data flow simplifies debugging with Redux DevTools. However, classic Redux involved extensive boilerplate, requiring action creators, constants, and complex update logic for each feature. Redux Toolkit (RTK) addresses this by providing sensible defaults, using Immer for simpler updates, and automatically generating actions from reducers, resulting in significantly less code while maintaining predictability.
Installing Redux Toolkit and React-Redux
Creating a Slice (the modern reducer)
A slice bundles together a piece of state, its reducers, and the actions that trigger them. Think of it as a self-contained module for a specific domain—like user, products, or cart.
Configuring the Store
The store is the central registry of all your application's state. You combine all your slices into a single root reducer and pass it to configureStore, which automatically sets up the Redux DevTools and middleware like Redux Thunk for async logic.
Providing the Store to Your React App
Wrap your entire application with the <Provider> component from React-Redux. This gives every component in the tree access to the store using hooks.
Using State and Dispatching Actions in Components
Inside any component, you read state with useSelector and send actions with useDispatch. The selector function subscribes to the Redux store and automatically re-renders your component only when the selected data changes—giving you fine-grained performance control without manual memoization.
Good Evening campers 💙
Part 1 --- Redux Toolkit (RTK)
Redux has been the standard for large-scale React applications for nearly a decade, centralizing application state in a single object called the "store." Instead of mutating this object directly, you dispatch "actions" that describe changes, and pure "reducers" compute the next state based on these actions. This unidirectional data flow simplifies debugging with Redux DevTools. However, classic Redux involved extensive boilerplate, requiring action creators, constants, and complex update logic for each feature. Redux Toolkit (RTK) addresses this by providing sensible defaults, using Immer for simpler updates, and automatically generating actions from reducers, resulting in significantly less code while maintaining predictability.
Installing Redux Toolkit and React-Redux
npm install @reduxjs/toolkit react-redux
Creating a Slice (the modern reducer)
A slice bundles together a piece of state, its reducers, and the actions that trigger them. Think of it as a self-contained module for a specific domain—like user, products, or cart.
// store/userSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
name: "Guest",
isLoggedIn: false,
preferences: { theme: "dark" }
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
login: (state, action) => {
// Thanks to Immer, we can "mutate" the state directly!
state.name = action.payload.name;
state.isLoggedIn = true;
},
logout: (state) => {
state.name = "Guest";
state.isLoggedIn = false;
},
toggleTheme: (state) => {
state.preferences.theme = state.preferences.theme === "dark" ? "light" : "dark";
}
}
});
// Export the generated action creators
export const { login, logout, toggleTheme } = userSlice.actions;
// Export the reducer to be included in the store
export default userSlice.reducer;
Configuring the Store
The store is the central registry of all your application's state. You combine all your slices into a single root reducer and pass it to configureStore, which automatically sets up the Redux DevTools and middleware like Redux Thunk for async logic.
// store/index.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
import cartReducer from "./cartSlice";
export const store = configureStore({
reducer: {
user: userReducer,
cart: cartReducer
}
});
Providing the Store to Your React App
Wrap your entire application with the <Provider> component from React-Redux. This gives every component in the tree access to the store using hooks.
jsx
// main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./store";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<Provider store={store}>
<App />
</Provider>
);
Using State and Dispatching Actions in Components
Inside any component, you read state with useSelector and send actions with useDispatch. The selector function subscribes to the Redux store and automatically re-renders your component only when the selected data changes—giving you fine-grained performance control without manual memoization.
jsx
// components/UserProfile.jsx
import { useSelector, useDispatch } from "react-redux";
import { login, logout, toggleTheme } from "../store/userSlice";
function UserProfile() {
const dispatch = useDispatch();
const { name, isLoggedIn, preferences } = useSelector((state) => state.user);
const handleLogin = () => {
dispatch(login({ name: "Megersa" }));
};
return (
<div>
<p>Welcome, {name}!</p>
<p>Theme: {preferences.theme}</p>
<button onClick={handleLogin}>Login</button>
<button onClick={() => dispatch(logout())}>Logout</button>
<button onClick={() => dispatch(toggleTheme())}>Toggle Theme</button>
</div>
);
}
Part 2 --- Zustand: The Minimalist Powerhouse
Zustand (German for "state") provides global state management without the complexity of Redux. Instead of using providers and separate action/reducer files, Zustand allows you to create a store with a single custom hook that defines state and mutation functions through a simple API. There are no dispatch functions or boilerplate reducers—just a plain JavaScript object with methods for updates. This simplicity leads to a shallower learning curve and faster prototyping. Zustand also optimizes component re-renders by preventing updates unless the relevant state changes, making it ideal for mid-sized applications and MVPs.
Installing Zustand
Creating a Store with Zustand
Stores are created using the create function. You provide a callback that receives set and get functions, and returns your state object along with methods to modify it. Notice how everything lives in one cohesive block—no separate actions or reducers.
Using Zustand in Components
To use the store inside a component, you invoke the custom hook and destructure exactly the pieces you need. Zustand's selector pattern ensures your component only re-renders when those specific fields change—similar to Redux's useSelector but built-in.
Zustand (German for "state") provides global state management without the complexity of Redux. Instead of using providers and separate action/reducer files, Zustand allows you to create a store with a single custom hook that defines state and mutation functions through a simple API. There are no dispatch functions or boilerplate reducers—just a plain JavaScript object with methods for updates. This simplicity leads to a shallower learning curve and faster prototyping. Zustand also optimizes component re-renders by preventing updates unless the relevant state changes, making it ideal for mid-sized applications and MVPs.
Installing Zustand
bash
npm install zustand
Creating a Store with Zustand
Stores are created using the create function. You provide a callback that receives set and get functions, and returns your state object along with methods to modify it. Notice how everything lives in one cohesive block—no separate actions or reducers.
jsx
// store/useUserStore.js
import { create } from "zustand";
const useUserStore = create((set, get) => ({
// State
name: "Guest",
isLoggedIn: false,
preferences: { theme: "dark" },
todos: [],
// Actions (methods that update state)
login: (name) => set({ name, isLoggedIn: true }),
logout: () => set({ name: "Guest", isLoggedIn: false }),
toggleTheme: () => set((state) => ({
preferences: {
...state.preferences,
theme: state.preferences.theme === "dark" ? "light" : "dark"
}
})),
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: Date.now(), text, done: false }]
})),
// Using get to access current state inside actions
getTodoCount: () => get().todos.length
}));
export default useUserStore;
Using Zustand in Components
To use the store inside a component, you invoke the custom hook and destructure exactly the pieces you need. Zustand's selector pattern ensures your component only re-renders when those specific fields change—similar to Redux's useSelector but built-in.
jsx
// components/Dashboard.jsx
import useUserStore from "../store/useUserStore";
function Dashboard() {
// Select only what you need—no unnecessary re-renders!
const { name, isLoggedIn, login, logout, toggleTheme, todos } = useUserStore();
// Or select a single field with a selector function
const todoCount = useUserStore((state) => state.todos.length);
return (
<div>
<p>User: {name}</p>
<p>Todo count: {todoCount}</p>
<button onClick={() => login("Megersa")}>Login</button>
<button onClick={logout}>Logout</button>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
🧩 Week 9 Day 5 Challenges
Challenge 1: Bookmark Manager with Redux Toolkit & Express
Build a bookmark storage app where users can save, tag, and organize their favorite links.
Requirements:
➜ Set up an Express server with GET /api/bookmarks, POST /api/bookmarks, and DELETE /api/bookmarks/:id endpoints. Use an in-memory array (or JSON file) for storage.
➜ Use Redux Toolkit to create a bookmarksSlice with createAsyncThunk for fetching, adding, and deleting bookmarks from your Express backend.
➜ Configure the Redux store with the bookmarks reducer and provide it to your app via <Provider>.
➜ Create a BookmarkList component that uses useSelector and useDispatch to display the bookmarks, show loading/error states, and handle form submission for adding new links.
Challenge 2: Habit Tracker Dashboard with Zustand & LocalStorage Sync
Track daily habits with persistence and a clean dashboard—no backend required for this one, just Zustand plus localStorage.
Requirements:
➜ Create a Zustand store called useHabitStore with state: habits (array), loading (boolean), error (string). Add actions: fetchHabits (reads from localStorage), addHabit, toggleHabit (mark done/undone), and deleteHabit.
➜ Use a custom hook useLocalStorage inside your Zustand actions to sync the habits array to localStorage automatically after every mutation. On initial load, fetchHabits should pull from localStorage.
➜ Use useMemo inside the HabitStats component to calculate the completion rate, current streak, and total active habits without recalculating on every habit toggle.
➜ Use useTransition to mark the habit filter (All / Active / Completed) as a low-priority update, ensuring the toggle buttons remain responsive even with 100 habits.
➜ Use createPortal to render a "Quick Add" modal that floats above the dashboard when the user presses the + button.
Challenge 3: Recipe Finder with RTK Query & Express
Build a recipe discovery app that searches a remote API (or your own Express mock) and caches results intelligently.
Requirements:
➜ Set up an Express server with GET /api/recipes?search=query that returns an array of recipe objects (use a mock dataset or the Spoonacular API).
➜ Use RTK Query's createApi with fetchBaseQuery to define a getRecipes query endpoint and a saveRecipe mutation endpoint for saving favorites.
➜ Use the auto-generated useGetRecipesQuery hook in your SearchPage component. Pass the search term as a query parameter. RTK Query will automatically cache results.
➜ Use useGetRecipesQuery's isFetching and isError flags to show loading spinners and error messages gracefully.
➜ Use useMemo to compute a list of unique recipe categories from the returned data to display as filter chips.
➜ Use useCallback to memoize the search handler that updates the search term state.
➜ Lazy-load the RecipeDetail page using React.lazy and <Suspense> so the user only loads the heavy description and image gallery when they click a recipe.
➜ Use NavLink with active styling to highlight the "Search" and "Favorites" navigation items.
When you are done,
💥 Share your solutions,
💥 invite a friend,
and as always —
💥 stay well, stay curious, and stay coding ✌️
Challenge 1: Bookmark Manager with Redux Toolkit & Express
Build a bookmark storage app where users can save, tag, and organize their favorite links.
Requirements:
➜ Set up an Express server with GET /api/bookmarks, POST /api/bookmarks, and DELETE /api/bookmarks/:id endpoints. Use an in-memory array (or JSON file) for storage.
➜ Use Redux Toolkit to create a bookmarksSlice with createAsyncThunk for fetching, adding, and deleting bookmarks from your Express backend.
➜ Configure the Redux store with the bookmarks reducer and provide it to your app via <Provider>.
➜ Create a BookmarkList component that uses useSelector and useDispatch to display the bookmarks, show loading/error states, and handle form submission for adding new links.
Challenge 2: Habit Tracker Dashboard with Zustand & LocalStorage Sync
Track daily habits with persistence and a clean dashboard—no backend required for this one, just Zustand plus localStorage.
Requirements:
➜ Create a Zustand store called useHabitStore with state: habits (array), loading (boolean), error (string). Add actions: fetchHabits (reads from localStorage), addHabit, toggleHabit (mark done/undone), and deleteHabit.
➜ Use a custom hook useLocalStorage inside your Zustand actions to sync the habits array to localStorage automatically after every mutation. On initial load, fetchHabits should pull from localStorage.
➜ Use useMemo inside the HabitStats component to calculate the completion rate, current streak, and total active habits without recalculating on every habit toggle.
➜ Use useTransition to mark the habit filter (All / Active / Completed) as a low-priority update, ensuring the toggle buttons remain responsive even with 100 habits.
➜ Use createPortal to render a "Quick Add" modal that floats above the dashboard when the user presses the + button.
Challenge 3: Recipe Finder with RTK Query & Express
Build a recipe discovery app that searches a remote API (or your own Express mock) and caches results intelligently.
Requirements:
➜ Set up an Express server with GET /api/recipes?search=query that returns an array of recipe objects (use a mock dataset or the Spoonacular API).
➜ Use RTK Query's createApi with fetchBaseQuery to define a getRecipes query endpoint and a saveRecipe mutation endpoint for saving favorites.
➜ Use the auto-generated useGetRecipesQuery hook in your SearchPage component. Pass the search term as a query parameter. RTK Query will automatically cache results.
➜ Use useGetRecipesQuery's isFetching and isError flags to show loading spinners and error messages gracefully.
➜ Use useMemo to compute a list of unique recipe categories from the returned data to display as filter chips.
➜ Use useCallback to memoize the search handler that updates the search term state.
➜ Lazy-load the RecipeDetail page using React.lazy and <Suspense> so the user only loads the heavy description and image gallery when they click a recipe.
➜ Use NavLink with active styling to highlight the "Search" and "Favorites" navigation items.
When you are done,
💥 Share your solutions,
💥 invite a friend,
and as always —
💥 stay well, stay curious, and stay coding ✌️
❤1
Performance Testing
Website performance directly impacts user retention, conversion rates, and search engine rankings - studies show that a 1-second delay can reduce conversions by 7% and customer satisfaction by 16%.
Slow sites also hurt Core Web Vitals, which Google uses as ranking signals, meaning poor performance pushes your pages down in search results.
Beyond SEO, performance affects accessibility: users on slow connections or older devices (common in emerging markets) will abandon a laggy site within 3 seconds. By proactively testing with DevTools, you catch bottlenecks like render-blocking scripts, oversized images, or memory leaks before they reach production, saving engineering hours and preserving brand trust.
Using DevTools as Your Performance Lab
Chrome DevTools gives you a complete performance toolkit without third-party costs.
Start in the Performance tab: hit record, interact with your page (scroll, click, type), stop, and analyze the flame chart - red bars indicate long tasks that block the main thread, while yellow spikes reveal heavy JavaScript or layout recalculations.
Pair this with Lighthouse for automated audits that grade your site and provide actionable fixes (e.g., "defer offscreen images" or "reduce unused CSS").
Use the Network tab's waterfall to spot slow API calls or large assets, and throttle CPU/network to simulate 4G or mid-tier phones.
Finally, check the Coverage tab to find dead code you can strip. Make this a habit: test after every significant change, set performance budgets (e.g., LCP under 2.5s), and treat DevTools as your first line of defense - not a last-minute panic tool.
Website performance directly impacts user retention, conversion rates, and search engine rankings - studies show that a 1-second delay can reduce conversions by 7% and customer satisfaction by 16%.
Slow sites also hurt Core Web Vitals, which Google uses as ranking signals, meaning poor performance pushes your pages down in search results.
Beyond SEO, performance affects accessibility: users on slow connections or older devices (common in emerging markets) will abandon a laggy site within 3 seconds. By proactively testing with DevTools, you catch bottlenecks like render-blocking scripts, oversized images, or memory leaks before they reach production, saving engineering hours and preserving brand trust.
Using DevTools as Your Performance Lab
Chrome DevTools gives you a complete performance toolkit without third-party costs.
Start in the Performance tab: hit record, interact with your page (scroll, click, type), stop, and analyze the flame chart - red bars indicate long tasks that block the main thread, while yellow spikes reveal heavy JavaScript or layout recalculations.
Pair this with Lighthouse for automated audits that grade your site and provide actionable fixes (e.g., "defer offscreen images" or "reduce unused CSS").
Use the Network tab's waterfall to spot slow API calls or large assets, and throttle CPU/network to simulate 4G or mid-tier phones.
Finally, check the Coverage tab to find dead code you can strip. Make this a habit: test after every significant change, set performance budgets (e.g., LCP under 2.5s), and treat DevTools as your first line of defense - not a last-minute panic tool.
React Hook Ecosystem – Quick Mapping Cheatsheet
React's built-in hooks are primitives. For real-world apps, replace them with these specialized libraries:
✔️ Data Fetching (instead of
✔️ Global Client State (instead of
✔️ Forms (instead of
✔️ Complex Workflows (instead of nested
✔️ Routing (instead of manual URL parsing) → TanStack Router (type-safe) or React Router.
✔️ Memoization (instead of overusing
✔️ Debouncing (instead of manual timeouts) → use-debounce hook.
✔️ Persistence (instead of manual localStorage reads/writes) → Zustand
React's built-in hooks are primitives. For real-world apps, replace them with these specialized libraries:
✔️ Data Fetching (instead of
useEffect + useState) → TanStack Query (or SWR). Handles caching, retries, and background refetching.✔️ Global Client State (instead of
useContext + useReducer) → Zustand (simplest) or Redux Toolkit (enterprise) or Jotai (atomic). Avoids the re-render hell of Context.✔️ Forms (instead of
useState per input) → React Hook Form. Uses refs, prevents re-renders on keystrokes.✔️ Complex Workflows (instead of nested
useEffect chains) → XState. Turns logic into testable state machines.✔️ Routing (instead of manual URL parsing) → TanStack Router (type-safe) or React Router.
✔️ Memoization (instead of overusing
useMemo/useCallback) → Rely on Zustand/Jotai selectors to subscribe to only the state slices you need, eliminating most manual memoization.✔️ Debouncing (instead of manual timeouts) → use-debounce hook.
✔️ Persistence (instead of manual localStorage reads/writes) → Zustand
persist middleware for auto-sync.❤1
Forwarded from TechVibe
Article of the day
Idempotency pattern
A payment request can time out even after the server has successfully processed it. Since the client can't tell whether the operation completed, it retries the request. Without idempotency, that retry can result in the customer being charged twice.
The solution is an idempotency key. For every payment, the client generates a unique identifier (typically a UUID) and sends it with the request. The server stores that key and the payment result within the same database transaction. If the client retries using the same key, the server returns the previously stored response instead of executing the payment again.
Idempotency keys shouldn't be stored forever. They should remain valid for at least as long as clients are expected to retry. A 24-hour TTL is a common default, balancing protection against duplicate requests with storage efficiency.
For production systems, every state-changing endpoint should require an idempotency key. Replayed requests should be checked before rate limiting since they're retries rather than new operations. Expired keys should be cleaned up periodically using a TTL, with an index on the expiration column to make cleanup efficient. The idempotency record should also maintain states such as PENDING, COMPLETED, and FAILED to handle retries and failure scenarios correctly. Finally, a reaper can safely remove requests that remain in the PENDING state beyond the expected request timeout, preventing abandoned operations from blocking future retries.
Read full article 👉 [LINK]
@devwitheyob
#TechVibe #ArticleOfTheDay #IdempotencyPattern #DistrubutedSystems
Idempotency pattern
A payment request can time out even after the server has successfully processed it. Since the client can't tell whether the operation completed, it retries the request. Without idempotency, that retry can result in the customer being charged twice.
The solution is an idempotency key. For every payment, the client generates a unique identifier (typically a UUID) and sends it with the request. The server stores that key and the payment result within the same database transaction. If the client retries using the same key, the server returns the previously stored response instead of executing the payment again.
Idempotency keys shouldn't be stored forever. They should remain valid for at least as long as clients are expected to retry. A 24-hour TTL is a common default, balancing protection against duplicate requests with storage efficiency.
For production systems, every state-changing endpoint should require an idempotency key. Replayed requests should be checked before rate limiting since they're retries rather than new operations. Expired keys should be cleaned up periodically using a TTL, with an index on the expiration column to make cleanup efficient. The idempotency record should also maintain states such as PENDING, COMPLETED, and FAILED to handle retries and failure scenarios correctly. Finally, a reaper can safely remove requests that remain in the PENDING state beyond the expected request timeout, preventing abandoned operations from blocking future retries.
Read full article 👉 [LINK]
@devwitheyob
#TechVibe #ArticleOfTheDay #IdempotencyPattern #DistrubutedSystems
❤1
How a Browser Turns a Website into a Screen You Can See
When you type a website address (like
1. Getting the files (Network)
First, the browser finds the server where the website lives and asks for its main file (the HTML document). Once the server sends the file, the browser receives it as a stream of raw data.
2. Reading the HTML (Parsing)
The browser reads this raw data and turns it into a family tree of elements, called the DOM. This tree shows all the content on the page (headings, paragraphs, images, etc.).
While reading, the browser often finds links to other files, like CSS files (which control style and color) and JavaScript files (which control behavior). It starts downloading these extra files at the same time, without waiting to finish reading the HTML.
3. Reading the Styles (CSSOM)
The browser takes any downloaded CSS files and turns them into another tree, called the CSSOM. This tree knows how every element should look- its colors, sizes, and fonts.
4. Combining Content and Styles (Render Tree)
Now, the browser merges the content tree (DOM) and the style tree (CSSOM) into a new list called the Render Tree. This list only includes the elements that will actually be visible on screen. For example, if an element has
5. Calculating Positions (Layout)
Next, the browser figures out exactly where every visible element should sit on your screen and how big it should be. This step is called layout. It's like measuring where to put every piece of furniture in a room.
6. Drawing the Pixels (Paint)
The browser now knows what to show and where to show it. So, it starts drawing the pixels. It breaks the page into separate layers (for things like menus that stay in place or animations) to make things run smoother.
7. Putting It All on Screen (Compositing)
The browser sends these drawn layers to your computer’s graphics card (GPU), which puts them all together like a collage and finally displays the page on your screen. This makes scrolling and animations look smooth.
8. Running JavaScript (The Event Loop)
JavaScript, the language that makes web pages interactive, runs on a single line - one task at a time. The browser handles these tasks using something called an event loop. It keeps a queue of tasks (like clicking a button or loading a file) and processes them one by one.
However, there's a catch: when the browser finds a regular JavaScript file in the HTML, it stops reading the HTML until that file is downloaded and run. Why? Because the script might change the page’s content, and the browser needs to be ready for that. (Modern browsers can use special tags like
9. Speed Matters (The Goal)
All of these steps—from reading HTML to showing the page—need to happen very fast, ideally in less than 1/60th of a second. This is what makes animations look smooth and the page feel responsive.
10. Constant Updates
The browser doesn't just do this once. Every time you click, scroll, type, or an animation runs, the browser repeats some of these steps—but only on the parts of the page that changed, not the whole thing. It uses different background threads (like a main thread, a compositor thread, and network threads) to keep everything running quickly and smoothly without freezing your screen.
When you type a website address (like
google.com) into your browser and press Enter, the browser does a lot of work behind the scenes to show you the page. Here’s what happens, step by step:1. Getting the files (Network)
First, the browser finds the server where the website lives and asks for its main file (the HTML document). Once the server sends the file, the browser receives it as a stream of raw data.
2. Reading the HTML (Parsing)
The browser reads this raw data and turns it into a family tree of elements, called the DOM. This tree shows all the content on the page (headings, paragraphs, images, etc.).
While reading, the browser often finds links to other files, like CSS files (which control style and color) and JavaScript files (which control behavior). It starts downloading these extra files at the same time, without waiting to finish reading the HTML.
3. Reading the Styles (CSSOM)
The browser takes any downloaded CSS files and turns them into another tree, called the CSSOM. This tree knows how every element should look- its colors, sizes, and fonts.
4. Combining Content and Styles (Render Tree)
Now, the browser merges the content tree (DOM) and the style tree (CSSOM) into a new list called the Render Tree. This list only includes the elements that will actually be visible on screen. For example, if an element has
display: none, it’s left out entirely.5. Calculating Positions (Layout)
Next, the browser figures out exactly where every visible element should sit on your screen and how big it should be. This step is called layout. It's like measuring where to put every piece of furniture in a room.
6. Drawing the Pixels (Paint)
The browser now knows what to show and where to show it. So, it starts drawing the pixels. It breaks the page into separate layers (for things like menus that stay in place or animations) to make things run smoother.
7. Putting It All on Screen (Compositing)
The browser sends these drawn layers to your computer’s graphics card (GPU), which puts them all together like a collage and finally displays the page on your screen. This makes scrolling and animations look smooth.
8. Running JavaScript (The Event Loop)
JavaScript, the language that makes web pages interactive, runs on a single line - one task at a time. The browser handles these tasks using something called an event loop. It keeps a queue of tasks (like clicking a button or loading a file) and processes them one by one.
However, there's a catch: when the browser finds a regular JavaScript file in the HTML, it stops reading the HTML until that file is downloaded and run. Why? Because the script might change the page’s content, and the browser needs to be ready for that. (Modern browsers can use special tags like
async or defer to avoid this delay.)9. Speed Matters (The Goal)
All of these steps—from reading HTML to showing the page—need to happen very fast, ideally in less than 1/60th of a second. This is what makes animations look smooth and the page feel responsive.
10. Constant Updates
The browser doesn't just do this once. Every time you click, scroll, type, or an animation runs, the browser repeats some of these steps—but only on the parts of the page that changed, not the whole thing. It uses different background threads (like a main thread, a compositor thread, and network threads) to keep everything running quickly and smoothly without freezing your screen.
Forwarded from AAU Software Engineering community (Messi Bre)
Please open Telegram to view this post
VIEW IN TELEGRAM
🌟 Week 9 Day 6 --- The Final Frontier: Axios, JWT & Full-Stack MERN
Part 1 --- JWT Authentication Flow (Understanding the Backend)
JSON Web Tokens are the modern standard for securing stateless APIs, particularly in MERN stacks where the frontend and backend are decoupled. The process starts when a user submits their credentials to a login endpoint; the Express server validates them, creates a signature using a secret key and the user's payload, and returns this signature as a long encoded string.
The frontend application persists this token, typically inside localStorage or sessionStorage, allowing the user's session to survive even after closing the browser tab. For every protected request, the frontend reads this token and attaches it as a Bearer token in the HTTP headers. Upon receiving it, the backend decodes and verifies the signature, extracting the user's ID and permissions without needing to query the database for every request, making authentication both secure and incredibly fast.
Example Backend Routes (Express)
While this is a frontend lesson, understanding the backend contract is essential. Here's a simplified Express setup for context:
Frontend API Service Functions
We'll create a dedicated service file that uses our Axios instance to interact with these endpoints.
Part 1 --- JWT Authentication Flow (Understanding the Backend)
JSON Web Tokens are the modern standard for securing stateless APIs, particularly in MERN stacks where the frontend and backend are decoupled. The process starts when a user submits their credentials to a login endpoint; the Express server validates them, creates a signature using a secret key and the user's payload, and returns this signature as a long encoded string.
The frontend application persists this token, typically inside localStorage or sessionStorage, allowing the user's session to survive even after closing the browser tab. For every protected request, the frontend reads this token and attaches it as a Bearer token in the HTTP headers. Upon receiving it, the backend decodes and verifies the signature, extracting the user's ID and permissions without needing to query the database for every request, making authentication both secure and incredibly fast.
Example Backend Routes (Express)
While this is a frontend lesson, understanding the backend contract is essential. Here's a simplified Express setup for context:
js
// server/server.js (Express)
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const User = require("./models/User");
app.post("/api/auth/register", async (req, res) => {
const { email, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({ email, password: hashedPassword });
await user.save();
res.status(201).json({ message: "User created" });
});
app.post("/api/auth/login", async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: "Invalid credentials" });
}
// Sign a JWT with the user's ID and email, expiring in 1 hour
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token, user: { id: user._id, email: user.email } });
});
Frontend API Service Functions
We'll create a dedicated service file that uses our Axios instance to interact with these endpoints.
jsx
// services/authService.js
import axiosInstance from "../utils/axiosInstance";
export const register = async (email, password) => {
const response = await axiosInstance.post("/auth/register", { email, password });
return response.data;
};
export const login = async (email, password) => {
const response = await axiosInstance.post("/auth/login", { email, password });
// Store the token and user data immediately upon success
const { token, user } = response.data;
localStorage.setItem("accessToken", token);
localStorage.setItem("user", JSON.stringify(user));
return response.data;
};
export const getProfile = async () => {
const response = await axiosInstance.get("/auth/profile"); // protected route
return response.data;
};
Hello campers 💙
Hope your summer is going well.
Big apologies for the post delayments 🙏
since we are finilizing our journey , from now on the contents will be short and introductory about deployments , security issues, AI , other stacks as a general.....
Hope your summer is going well.
Big apologies for the post delayments 🙏
since we are finilizing our journey , from now on the contents will be short and introductory about deployments , security issues, AI , other stacks as a general.....
❤1
Part 2 --- Axios Deep Dive: Instances & Interceptors
While the native fetch API handles basic requests adequately, mature applications demand a much more robust HTTP client. Axios provides a superior API with request and response interceptor capabilities that act as middleware for every network call that leaves your browser. A request interceptor allows you to inspect, modify, or entirely cancel a request before it reaches the server, which is the perfect hook to inject your JWT token into the Authorization header automatically. Meanwhile, a response interceptor lets you globally handle errors like expired tokens, network failures, or server maintenance without cluttering your UI components with repetitive try/catch blocks. By centralizing this logic, Axios becomes the nervous system of your application, ensuring every interaction with the backend is smooth and secure.
Installing Axios
Creating an Axios Instance
Instead of writing the full http://localhost:5000/api URL in every component, we create a pre-configured instance. This instance holds the base URL and default headers, ensuring consistency across your entire codebase.
Request Interceptor --- Automatically Attaching the Token
This interceptor runs right before any request is sent. It pulls the JWT from localStorage and attaches it to the Authorization header. This means your components don't need to remember to pass the token—Axios handles it invisibly for every secured endpoint.
Response Interceptor --- Global Error Handling & Token Expiry
This interceptor catches the response before it reaches your component's .catch() block. If the server returns a 401 Unauthorized status (meaning the token is invalid or expired), we can clear the user session and redirect to the login page in a single centralized location. This saves you from writing if (error.status === 401) in every single API call you make.
While the native fetch API handles basic requests adequately, mature applications demand a much more robust HTTP client. Axios provides a superior API with request and response interceptor capabilities that act as middleware for every network call that leaves your browser. A request interceptor allows you to inspect, modify, or entirely cancel a request before it reaches the server, which is the perfect hook to inject your JWT token into the Authorization header automatically. Meanwhile, a response interceptor lets you globally handle errors like expired tokens, network failures, or server maintenance without cluttering your UI components with repetitive try/catch blocks. By centralizing this logic, Axios becomes the nervous system of your application, ensuring every interaction with the backend is smooth and secure.
Installing Axios
npm install axios
Creating an Axios Instance
Instead of writing the full http://localhost:5000/api URL in every component, we create a pre-configured instance. This instance holds the base URL and default headers, ensuring consistency across your entire codebase.
// utils/axiosInstance.js
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "http://localhost:5000/api",
timeout: 10000, // 10 seconds
headers: {
"Content-Type": "application/json",
},
});
export default axiosInstance;
Request Interceptor --- Automatically Attaching the Token
This interceptor runs right before any request is sent. It pulls the JWT from localStorage and attaches it to the Authorization header. This means your components don't need to remember to pass the token—Axios handles it invisibly for every secured endpoint.
// utils/axiosInstance.js (continued)
axiosInstance.interceptors.request.use(
(config) => {
const token = localStorage.getItem("accessToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
Response Interceptor --- Global Error Handling & Token Expiry
This interceptor catches the response before it reaches your component's .catch() block. If the server returns a 401 Unauthorized status (meaning the token is invalid or expired), we can clear the user session and redirect to the login page in a single centralized location. This saves you from writing if (error.status === 401) in every single API call you make.
jsx
// utils/axiosInstance.js (continued)
axiosInstance.interceptors.response.use(
(response) => response, // Just pass successful responses through
async (error) => {
const originalRequest = error.config;
// Check if error is 401 and we haven't retried yet
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// Optional: Refresh token logic could go here
// For now, we just log out the user
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
// Redirect to login page (React Router navigation will be triggered via event)
window.location.href = "/login";
return Promise.reject(error);
}
return Promise.reject(error);
}
);
Part 3 --- Protected Routes & Authorization (Frontend)
Frontend route protection is the user-facing gatekeeper of your application, ensuring unauthorized visitors cannot manually type URLs to access restricted dashboards or admin panels. The pattern involves creating a wrapper component, conventionally named ProtectedRoute, that checks the global authentication state—usually sourced from a Context or Zustand store that synchronizes with localStorage. While the authentication status is being verified (for instance, if the token exists but we haven't fetched the user's profile yet), the wrapper renders a loading spinner to avoid the visual flicker of redirecting from a login page. If the user is authenticated, the wrapper renders its child components (typically using Outlet in React Router v6 for nested routes). If not, it imperatively navigates the user back to the login screen using useNavigate, creating a seamless and secure browsing experience.
Creating an Auth Context (Global User State)
We need a global state to hold the current user and loading status. We'll use the Context API (or Zustand) so that the Navbar, ProtectedRoute, and any component can access the authentication status instantly.
Implementing the ProtectedRoute Component
This component uses the useAuth hook to determine if the user is authenticated. If loading is true, we show a spinner. If user is null, we redirect to /login. Otherwise, we render the child routes.
Using in App.jsx
We structure our routes so that all private pages are nested inside the <ProtectedRoute> component.
Frontend route protection is the user-facing gatekeeper of your application, ensuring unauthorized visitors cannot manually type URLs to access restricted dashboards or admin panels. The pattern involves creating a wrapper component, conventionally named ProtectedRoute, that checks the global authentication state—usually sourced from a Context or Zustand store that synchronizes with localStorage. While the authentication status is being verified (for instance, if the token exists but we haven't fetched the user's profile yet), the wrapper renders a loading spinner to avoid the visual flicker of redirecting from a login page. If the user is authenticated, the wrapper renders its child components (typically using Outlet in React Router v6 for nested routes). If not, it imperatively navigates the user back to the login screen using useNavigate, creating a seamless and secure browsing experience.
Creating an Auth Context (Global User State)
We need a global state to hold the current user and loading status. We'll use the Context API (or Zustand) so that the Navbar, ProtectedRoute, and any component can access the authentication status instantly.
// context/AuthContext.jsx
import { createContext, useContext, useState, useEffect } from "react";
import { getProfile } from "../services/authService";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check if a token exists on app mount
const token = localStorage.getItem("accessToken");
if (token) {
// Verify the token by fetching the user profile
getProfile()
.then((userData) => setUser(userData))
.catch(() => {
// If token is invalid, clear it
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
setUser(null);
})
.finally(() => setLoading(false));
} else {
setLoading(false);
}
}, []);
const login = (userData, token) => {
localStorage.setItem("accessToken", token);
localStorage.setItem("user", JSON.stringify(userData));
setUser(userData);
};
const logout = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Implementing the ProtectedRoute Component
This component uses the useAuth hook to determine if the user is authenticated. If loading is true, we show a spinner. If user is null, we redirect to /login. Otherwise, we render the child routes.
// components/ProtectedRoute.jsx
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
const ProtectedRoute = () => {
const { user, loading } = useAuth();
if (loading) {
return <div className="spinner">Loading your session...</div>;
}
return user ? <Outlet /> : <Navigate to="/login" replace />;
};
export default ProtectedRoute;
Using in App.jsx
We structure our routes so that all private pages are nested inside the <ProtectedRoute> component.
// App.jsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import ProtectedRoute from "./components/ProtectedRoute";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import Profile from "./pages/Profile";
function App() {
return (
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
);
}
Part 4 --- Building the Full-Stack MERN Structure
Connecting the frontend and backend into a cohesive MERN application requires careful coordination of ports, CORS policies, and environment variables. The React development server typically runs on port 5173, while the Express server runs on port 5000, necessitating a proxy configuration or explicit CORS middleware to allow cross-origin requests. To make API endpoints maintainable, developers define a centralized API client that points to the base URL of the backend, ensuring that if your server IP changes, you only update one file. This full-stack structure brings a new level of organization, often separating concerns into frontend components, frontend state stores, backend routes, backend controllers, and database models. With this architecture, your application becomes highly modular, easily extensible, and ready for deployment to platforms like Render or Vercel.
Typical Project Folder Structure
Connecting Client to Server with CORS
On the backend, ensure CORS is enabled to accept requests from your React origin.
Environment Variables (.env)
Never hardcode secrets! Use environment variables for the API URL and JWT secret.
---
Connecting the frontend and backend into a cohesive MERN application requires careful coordination of ports, CORS policies, and environment variables. The React development server typically runs on port 5173, while the Express server runs on port 5000, necessitating a proxy configuration or explicit CORS middleware to allow cross-origin requests. To make API endpoints maintainable, developers define a centralized API client that points to the base URL of the backend, ensuring that if your server IP changes, you only update one file. This full-stack structure brings a new level of organization, often separating concerns into frontend components, frontend state stores, backend routes, backend controllers, and database models. With this architecture, your application becomes highly modular, easily extensible, and ready for deployment to platforms like Render or Vercel.
Typical Project Folder Structure
my-mern-app/
├── client/ # React Frontend (Vite)
│ ├── src/
│ │ ├── components/ # Reusable UI pieces
│ │ ├── pages/ # Route-level screens
│ │ ├── context/ # AuthContext, ThemeContext
│ │ ├── services/ # API service files (authService, productService)
│ │ ├── utils/ # axiosInstance.js, helpers
│ │ └── App.jsx
│ └── package.json
│
└── server/ # Express Backend
├── models/ # Mongoose models (User, Product)
├── routes/ # Express route handlers
├── controllers/ # Business logic
├── middleware/ # auth.js (verifyToken), errorHandler.js
├── config/ # Database connection
└── server.jsConnecting Client to Server with CORS
On the backend, ensure CORS is enabled to accept requests from your React origin.
// server/server.js
const cors = require("cors");
app.use(cors({ origin: "http://localhost:5173", credentials: true }));
Environment Variables (.env)
Never hardcode secrets! Use environment variables for the API URL and JWT secret.
# client/.env
VITE_API_URL=http://localhost:5000/api
# server/.env
PORT=5000
MONGODB_URI=mongodb://localhost:27017/myapp
JWT_SECRET=your_super_secret_key_here---
Part 5 --- Logout & Token Expiry Gracefully
Handling the end of a user's session gracefully is just as important as logging them in. When a user intentionally clicks logout, the application must immediately clear the stored token from localStorage and reset the global state to null, instantly reflecting the unauthenticated state across every component. However, sessions can also end unexpectedly when a JWT expires—modern tokens often include an exp claim that the server checks, returning a 401 HTTP status code if the time has passed. Our Axios response interceptor detects this specific 401 code, clears the stale token, and redirects the user to the login page, often displaying a friendly notification that their session has timed out. This proactive approach prevents dreaded "broken UI" scenarios where components try to fetch data with invalid credentials, ensuring the user is always met with a clear path back to regaining access.
The Logout Function (in AuthContext)
Handling Expiry with a Notification
We can enhance our Axios response interceptor to show a toast notification before redirecting, using a library like react-hot-toast.
Then, in your App.jsx, you listen for this event and navigate programmatically:
Handling the end of a user's session gracefully is just as important as logging them in. When a user intentionally clicks logout, the application must immediately clear the stored token from localStorage and reset the global state to null, instantly reflecting the unauthenticated state across every component. However, sessions can also end unexpectedly when a JWT expires—modern tokens often include an exp claim that the server checks, returning a 401 HTTP status code if the time has passed. Our Axios response interceptor detects this specific 401 code, clears the stale token, and redirects the user to the login page, often displaying a friendly notification that their session has timed out. This proactive approach prevents dreaded "broken UI" scenarios where components try to fetch data with invalid credentials, ensuring the user is always met with a clear path back to regaining access.
The Logout Function (in AuthContext)
// context/AuthContext.jsx (inside the provider)
const logout = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
setUser(null);
// Optionally navigate using useNavigate if called inside a component
};
Handling Expiry with a Notification
We can enhance our Axios response interceptor to show a toast notification before redirecting, using a library like react-hot-toast.
// utils/axiosInstance.js
import toast from "react-hot-toast";
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
toast.error("Your session has expired. Please login again.");
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
// Dispatch a custom event to let React Router know to redirect
window.dispatchEvent(new CustomEvent("unauthorized"));
}
return Promise.reject(error);
}
);
Then, in your App.jsx, you listen for this event and navigate programmatically:
// App.jsx (inside the component)
useEffect(() => {
const handleUnauthorized = () => {
navigate("/login", { replace: true });
};
window.addEventListener("unauthorized", handleUnauthorized);
return () => window.removeEventListener("unauthorized", handleUnauthorized);
}, [navigate]);
Forwarded from AAU Software Engineering community (Messi Bre)
Please open Telegram to view this post
VIEW IN TELEGRAM