If you call yourself a SWE, you do not need any introduction to what React.js is. For the non-tech folks, React is a JavaScript library used to build high performance web applications (but then again, what are you doing here?😂). Developed by Meta (then, Facebook), React powers some of the most widely used platforms, including Facebook itself. But why was React created in the first place? And more importantly, how did it come to revolutionize the way we build web applications? And more importantly, how does a website work? Let’s find out in the blog.
How does any website work?
Have you ever wondered how your browser works? What happens when we type a domain name, like www.google.com, into a browser? Let’s break it down step by step.
1. DNS (Domain Name System) Lookup
The first step is the DNS Lookup mechanism. This process involves identifying the IP address corresponding to the domain name we entered. Here’s how it works:
- Stub Resolver: The process begins with an in-device cache called the Stub Resolver. It stores previously resolved domain names. If the requested domain’s IP address is available in the cache, the process ends here, saving time and resources.
- Hierarchical Servers: If the Stub Resolver doesn’t have the required information, the device makes a recursive query to a hierarchy of DNS servers spread across the world. These servers form the backbone of the internet by translating human-readable domain names into machine-readable IP addresses.
The DNS Lookup Process:
- Root Servers: The first query is sent to the root servers, a set of 13 servers operated by organizations like NASA, the U.S. Department of Defense, and Verisign. These servers don’t store domain-specific IP addresses but direct the query to the appropriate top-level domain (TLD) servers based on the domain’s extension (.com, .org, .xyz, etc.).
- TLD Servers: The query is then passed to the TLD servers, which store information about domains with specific extensions. For example, if you are accessing a .com domain, these servers will point you to the next level of authoritative DNS servers.
- Authoritative DNS Servers: These servers have the actual IP address of the requested domain. They send the IP address back to the client.
This hierarchical process may involve multiple steps, but it is optimized for efficiency and redundancy. You can explore this process on a Linux system using the nslookup command.
2. IP Address and Communication Setup
An IP address is like a phone number assigned to every device connected to the internet. It ensures that data packets reach the correct destination. Once the IP address of the server hosting the requested domain is identified, the next step is to establish a secure communication channel between your device (the client) and the server.
Protocols Involved:
- TCP (Transmission Control Protocol): TCP ensures reliable delivery of data packets by establishing a connection between the client and server.
- TLS (Transport Layer Security): TLS adds a layer of encryption to ensure secure data exchange, protecting sensitive information like passwords or credit card details.
3. Handling Additional Assets and Static Files
Many websites rely on assets and static files (e.g., images, stylesheets, scripts) hosted on different servers. Each of these assets may require its own DNS lookup, which can introduce latency, especially for asset-heavy websites. Modern techniques like content delivery networks (CDNs) and caching are often used to minimize this delay and improve performance.
And while these steps sound straightforward, they involve highly complex and sophisticated systems working seamlessly to deliver the content you requested. Browsers and DNS mechanisms truly showcase the ingenuity behind modern internet technology.
For the Networking Nerds!
If you already do not know, the internet runs on this protocol called TCP or Transmission Control Protocol. It is nothing but a set of communication standards that establish a connection between the source and destination, break large amounts of data into smaller packets and implements congestion control, which starts with small requests and increases in size as needed.
If you’re familiar with SYN-ACK terminology, here’s the thing — the server must receive an acknowledgment from the client in the form of an ACK packet after a certain number of segments are sent.
Waiting for an ACK after each segment may increase transmission time, even in the case of a low-load network. Sending too many segments at once can also lead overloading, because the server would continue to respond in a sequential manner. To avoid this, a very famous algorithm called TCP Slow Start was devised. This algorithm allocates the maximum number of segments to be transmitted based on the maximum bandwidth of given network, thereby solving both the aforementioned problems.
Of course, with times, we engineers have developed a newer algorithm called TCP Fast Open, which relies on cryptographic cookies to reduce load times for already visited (and hence, connected) websites.
How Our Browsers Paint a Blank Canvas using Code
Once a secure client-server channel is set up, the server begins sending packets of data to the client, i.e., the web browser. These packets mainly consist of HTML, CSS, JavaScript, and other resources like images, videos, or fonts.
The moment a connection is securely established to a web server, the browser sends a GET request to receive the first chunk of data. By protocol, this mostly includes an HTML file, and is approximately 14kb is size. The time between sending the request, and receiving the first response is also called the Time to First Byte.
Populating a Web Page
How does a browser take this raw data and display a functional website on your screen? This process is called the Critical Rendering Path, and it works as follows.
Once the browser receives the first chunk of data, it can begin parsing the information received. Parsing is the step the browser takes to turn the data it receives over the network into the DOM, which is used by the renderer to paint a page to the screen.
The Document Object Model or DOM, is the internal representation of the HTML for the browser. It is an in-memory stored data structure that parses code, storing every part of the code in a tree-like format. Similar to DOM, there exists a similar data structure for CSS, termed CSSOM.
Even if the requested page’s HTML is larger than the initial 14KB packet, the browser will begin parsing and attempting to render an experience based on the data it has. This is why it’s important for web performance optimization to include everything the browser needs to start rendering a page, or at least a template of the page — the CSS and HTML needed for the first render — in the first 14KB. But before anything is rendered to the screen, the HTML, CSS, and JavaScript have to be parsed.
Let me break this whole process into 7 parts.
Parsing HTML
The browser starts by parsing the HTML to create the DOM (Document Object Model), which is a tree-like structure that represents the content and hierarchy of the HTML document. Parsing is done through tokenization and creating parse trees. The DOM acts as an interface (API) for the rest of the browser to interact with and manipulate the content of the page. In fact, developers can directly manipulate DOM.
Preload Scanner
While parsing the HTML, the browser identifies and begins to download external resources in the background (like CSS, JavaScript, and images) even before the parsing is fully complete. This is called the preload scanner, and it ensures that critical resources are downloaded as early as possible to speed up the rendering process. This is very commonly used in JavaScript using preload(ASSET_URL, {as: TYPE}); .
CSSOM (CSS Object Model) Construction
The browser then parses the CSS files to create the CSSOM, a tree structure that represents the styles of the document. The CSSOM works in tandem with the DOM to determine how the website should look. The browser will parse all CSS files it receives from a server, and construct a CSSOM.
Render Tree Construction
The browser combines the DOM and the CSSOM into a Render Tree, which represents the visible content of the webpage along with its applied styles. Any content that is hidden via CSS (e.g., display: none) or outside the viewport is excluded from the Render Tree. Another interesting thing is render-blocking processes. The steps I have mentioned above in an are sequential in nature, however certain conditions block the process at a step, execute another operation, and then continue with the sequence.
One such concept is called parser-blocking script. These are scripts that block the parsing process which runs on the main thread, download the new script, and run that script on the main thread, and then proceed as normal. These script files are usually JavaScript, and can either be embedded or external.
- From an external file specified by the src attribute of a <script> tag.
- Inline, between a pair of <script> and </script> tags.
- In an HTML event handler attribute, such as onclick or onmouseover.
- In a URL that uses the special JavaScript protocol.
What is the need to block? Can we not run then asynchronously?
We could, except not in all scenarios. If the browser wishes to run DOM parsing and script execution parallelly, then there could be race conditions between the DOM parser thread and the main thread which is why DOM parsing must happen on the main thread.
All normal scripts (embedded or external) are parser-blocking as they halt the construction of DOM. All async scripts (AKA asynchronous scripts) do not block parser until they are downloaded. As soon as an async script is downloaded, it becomes parser-blocking. However, all defer scripts (AKA deferred scripts) are non-parser-blocking script as they do not block the parser and execute after the DOM tree is fully constructed.
Layout (Reflow)
Once the Render Tree is created i.e, the intial set of scripts, along with HTML and CSS have been parsed and constructed, the browser calculates the exact positions and sizes of each element on the page. This process is known as layout or reflow. It ensures that all elements are placed correctly on the screen. This is done by parsing the Render Tree, taking account of styling rules such as display type (flex, inline, block, etc.), orientation, breakpoints (if any).
This is a very big bottleneck when it comes to performance but inevitable. This is because when browser recalculates the layout, it often has to re-render parts or the entire page.
Painting
The browser performs the action of painting by taking the layout tree (a structured representation of elements, their styles, and positions) and converting it into pixels on the screen.
First, the browser breaks the content into layers based on factors like overlapping elements (e.g., z-index), hardware-accelerated elements (e.g., animations), or isolated rendering contexts (e.g., position: fixed). Next, it generates a display list, which contains sequential instructions for painting each visual element, such as drawing shapes, text, images, and backgrounds. These instructions are processed during rasterization, where vector-based drawing commands are converted into pixel data. For performance optimization, the browser often divides layers into smaller tiles, rasterizing only visible portions to reduce computation. Hardware acceleration using the GPU is commonly employed to handle this rasterization process efficiently.
Compositing
Once rasterization is complete, the browser proceeds to the compositing phase, where it merges the rendered layers into a single final image, taking into account their stacking order and transparency. The composited image is stored in the GPU’s frame buffer and sent to the screen buffer for display. Modern browsers optimize this process by caching static layers, reusing previously painted content, and only repainting portions of the screen that have changed (partial invalidation).
The entire process leverages GPU acceleration to ensure smooth performance, especially for animations or scrolling. Finally, the painted result is displayed on the monitor, synchronized with the screen’s refresh rate (e.g., 60Hz), ensuring a seamless user experience. For complex pages with overlapping elements, animations, or layers, the browser composites these layers together to create the final visual representation of the page.
How is my Browser doing all this?
Well, in case you don’t know, web browsers and search engines run on very powerful and sophisticated backend engines (also known as Rendering Engine or Layout Engine). Since you’re most probably reading this on Google, let’s talk about Chrome. Chrome runs on something called the V8 Engine. V8 is a JavaScript engine written in C/C++, primarily used to parse and compile a website’s JavaScript code, enabling the user to interact with the website.
When you visit a website, here’s what happens behind the scenes:
- JavaScript Code Parsing:
The JavaScript code embedded in the page is sent to the V8 engine. V8 parses the code and generates an Abstract Syntax Tree (AST). For people who didn’t focus in their Compilers class (like me 😂), an AST is a tree-like representation of a code’s logic (loops, functions, if-else). This is used to verify the correctness of a program and generate symbol tables. - Bytecode Generation:
V8 has an in-built register-based interpreter called Ignition. Ignition converts the AST into bytecode, an intermediate representation that is quick to execute. It maintains and stores results in virtual registers instead of manipulating stack in the memory, which reduces overhead while performing long and redundant calculations. - Optimized Machine Code:
V8 offers a highly efficient and optimal JIT compiler called TurboFan. TurboFan is based on a concept called “Sea of Nodes” which implements graph reductions on the Control-flow and Data-flow graphs generated for a given piece of code. These optimization layers help generate better quality bytecode which the machine can interpret faster and more efficiently. (Read more here: https://darksi.de/d.sea-of-nodes/) - Event Handling and Execution:
V8 interacts with the browser’s event loop to execute JavaScript callbacks, handle user interactions (like button clicks), and manage asynchronous operations (like AJAX calls or timers). Event loop is in itself a very complex implementation which would require another blog, but in short, the event loop is a JavaScript runtime model responsible for executing the code, collecting and processing events, and executing queued sub-tasks. It is built into the browser’s engine. V8 is one such exception as it lacks its own event loop. V8 usually forwards the function to either the libuv API (a C based library that handles asynchronous calls) or Chrome’s internal event loop, depending on where the script is executed.
Mind you, this is happening in anywhere from a couple of milliseconds to a few seconds. Truly mind blowing I’d say!
Development of React
Before engineers even thought of React, web development was still in boom. But of course, not utterly customizable or efficient. Then came React, and look how it has changed the game. React, or any other dynamic JavaScript-based library works on something called virtual DOM. We will talk about this in the next chapter, but for now, think of it as something that reduces the load (number of operations) performed on the actual DOM, thereby ensuring high performance and responsiveness.
Before the idea of virtual DOM became streamlined, PHP was heavily used in production of websites. In fact, even Facebook was built on it. PHP released a new tool called XHP- an HTML component library for PHP, with the goal to create a more efficient way to build dynamic UIs. Taking inspiration from this, Facebook engineer Jordan Walke came up with something called FaxJS which did basically the same thing as XHP, but for JavaScript based codebases.
Facebook kept this technology under the hood, and released it publicly after it was implemented in Facebook’s News Feed. Sometimes, it only takes a man with inspiration to change the game.
Behind the Scenes in a React Application
If you’ve used React before, you notice that the you are no longer creating single .html or .js files. Instead, you have a structured directory with certain specific files that have very specific functions. You might know them as either page.tsx or App.js or main.js. All of them have functionalities we will talk about.
React enables the coder to write something called JSX, which stands for JavaScript XML. It is basically a mixture of JavaScript and HTML. It looks something like below:
React enables coders to break down a website into different components. For example, is a login page, each of the fields, each of buttons and images or containers are different components. The user can write separate JSX for all the components in different files, and then combine all of them in another file (this is usually your page.tsx in Next.js).
But you may ask, how does the machine understand to separate HTML from JavaScript functionality? This is done using a tool called Babel. Babel is a parser that converts JSX into JS using an API called React.createElement(). Read more here: https://babeljs.io/docs/.
Once this process is done, and all elements pertaining to a page are created, the next step is rendering these elements onto the DOM (Document Object Model). React does this efficiently using a virtual DOM.
Virtual DOM in React
The virtual DOM is a lightweight copy of the real DOM. When changes are made to a React component, the virtual DOM is updated first. React then calculates the difference (called diffing) between the old virtual DOM and the new virtual DOM. This is done using a heuristic string-based parsing algorithm called the diffing algorithm. Once the differences are identified, React updates only the necessary parts of the real DOM, making more efficient than directly interacting with the DOM. Remember that more efficient does not always mean faster.
React Lifecycle
Every React component goes through a lifecycle with three main phases:
- Mounting: When the component is created and added to the DOM (e.g., componentDidMount or useEffect for functional components).
- Updating: When changes occur to the component’s props or state.
- Unmounting: When the component is removed from the DOM (e.g., componentWillUnmount).
In newer and more complex frameworks, such as Next.js, these lifecycle methods come as in-built functionalities. They use techniques such as Server Side Rendering and Hydration, unlike default React.js applications where the user had to do some manual work. (Read this for more a detailed list of lifecycle APIs: https://legacy.reactjs.org/docs/react-component.html)
The Role of Bundlers
While Babel translates JSX into JavaScript, bundlers plays a crucial role in bundling all your JavaScript, CSS, and other assets into a single file (or multiple files) that can be served to the browser. The reason behind doing this is because by default, until ES2015, JavaScript did not offer import or export statements. Hence, users would resort to complex logic and global variables to pass dependencies from one file to the other which are not good practices.
You might ask then, why not use require()? Technical difficulties arise because require() is NOT asynchronous in nature. Hence, it needs all different scripts preloaded when it access them (via HTTP). The only feasible way to do this is to combine all code into a single file, and then serve that file. This is where the role of bundlers such as Webpack and browserify come. Over the years, webpack also manages dependencies and optimizes the code for production by minimizing and compressing files.
(Read more here: https://medium.com/@gimenete/how-javascript-bundlers-work-1fc0d0caf2da)
The React Rendering Process
Here’s a high-level overview of what happens behind the scenes when a React application runs:
- Entry Point: The application starts at an entry point file, typically index.js or main.js. This is where React initializes the app and renders it to the DOM using ReactDOM.createRoot() or ReactDOM.render().
- Component Tree: React processes the component tree, starting from the root component (e.g., App.js) and traversing down to child components. This parsing is similar to DOM construction.
- Reconciliation: React uses the virtual DOM to determine how the UI should update in response to changes in state or props. This includes using diffing to keep track of changes.
- Rendering: React updates the real DOM with the calculated changes, ensuring optimal performance. If you remember about Reflow, then yes, reflow is limited/reduced to a great extend with the use of virtual DOMs since calculations on the actual DOM are far less frequent.
Developments to React
It’s hard to believe that it’s been over a decade since React first came into existence. Over the years, this revolutionary library has only grown stronger, smarter, and faster. Development speeds have skyrocketed, especially with the rise of AI-powered copilots and code generation tools that enable anyone — yes, anyone — to create simple, functional websites within days.
Adding to this rapid innovation are technologies like Next.js, Tailwind CSS, and TypeScript, which have introduced modern concepts that further optimize the development process. Together, they’ve made the whole process so easy for anyone who’s starting out.
This incredible evolution is a testament to the beauty and diversity of the field of Computer Science which continues to push the boundaries of what’s possible. If you’ve never paused to marvel at how websites work, now you’re part of a cohort that does. And who knows? The insights you’ve gained from this blog may very well come in handy during your next System Design interview.
Ciao!