Next.js, Django, Spring Boot and Ruby on Rails look very different, but they answer the same small set of questions about how a web application works. This post maps those questions, so that you can recognise the problem before you learn one framework's answer to it.
Frameworks are answers to problems
Imagine you are building an online store.
At first, the job looks simple. You need a page that lists the products, a page that shows the details of one product, and a shopping cart.
Very soon, questions appear:
- How does the application know which page to show?
- Where do the products come from?
- How do you know which customer is which?
- How do you stop one customer from seeing another customer's orders?
- What happens when thousands of people visit the store at the same time?
- You change the price of a product, but some customers still see the old price. Why?
None of these are questions about JavaScript. They are questions about web applications. Developers have answered them for decades, in Python, Java, PHP (PHP: Hypertext Preprocessor, a language made for web servers), Ruby, C#, JavaScript and many other languages.
Over time, developers noticed that they kept writing similar code to solve the same problems. That repeated code turned into web frameworks. A web framework is a set of rules, tools and ready-made building blocks that solves the problems every web application has, so you don't have to solve them again yourself.
Frameworks solve these problems in different ways, but the problems themselves are almost the same everywhere. That is why this post starts with the problems and not with any one framework. Once you can see the problem, moving from Next.js to Django, Laravel or Spring Boot gets much easier. You still have to learn a new language, its libraries and the framework's habits, but you don't have to learn how the web works again from the start.
What Next.js adds on top of React
To see what a framework adds, it helps to look at one framework next to the library it is built on. Next.js and React are a good pair for this.
React is a library for building user interfaces. Its own documentation calls React "a JavaScript library for building interactive user interfaces" that "leaves it up to the developer where to use those functions in their application"[1]. React helps you describe what the user sees and how the screen changes when the data changes.
A complete web application needs much more than a screen. It needs addresses for its pages, a server, a way to read and save data, security, a way to avoid repeating slow work, error handling and a way to put it all online. React doesn't decide how you put those pieces together.
Next.js is built on React and makes many of those decisions for you. The same page describes Next.js as a framework that "handles the tooling and configuration needed for React, and provides additional structure, features, and optimizations"[1].
A few terms appear in the table below. A URL (Uniform Resource Locator) is the address of a page, like https://shop.example.com/products/42. Rendering means turning data into the HTML (HyperText Markup Language) that the browser draws on the screen. An endpoint is a URL that another program calls to get or send data, instead of a person reading a page.
| Problem | What Next.js gives you |
|---|---|
| Which page answers a URL? | Routing (the App Router) |
| Is the page rendered on the server or in the browser? | Server rendering and browser rendering |
| How do we get the data? | Ways to read data on the server |
| How do we avoid doing the same work again? | A cache, and ways to refresh it |
| How do we create endpoints? | Route Handlers |
| How do users send changes? | Server Functions and Server Actions |
| How do we send images quickly? | Image optimisation |
| How do we move between pages quickly? | Navigation in the browser, and loading the next page early |
| How do we act on a request before it reaches a page? | Proxy (called Middleware before Next.js 16)[2] |
| How do we build the application and put it online? | A build step and links to hosting platforms |
Next.js doesn't do everything. For example, it has no built-in database and no complete login system. Its own guide says its server features "are not a full backend replacement"[3]. Instead, it gives you a base to start from and clear places to connect the database, the login service and the other parts you choose.
This shows the most important idea in the whole post: a responsibility (a job the application must do) is different from an implementation (one particular tool that does that job).
- Routing is a responsibility. The Next.js App Router is one implementation of it.
- Caching is a responsibility. The Next.js cache functions are one implementation of it.
- Storing data is a responsibility. PostgreSQL, MongoDB and SQLite are three implementations of it.
Every framework is a collection of implementations. If you learn the responsibilities, each new framework becomes a new set of tools for jobs you already know.
The most used web frameworks in 2026
Next.js is only one member of a large family. Some frameworks focus on showing web pages. Others focus on building APIs (application programming interfaces: the endpoints that other programs call to read or change data). Others cover almost everything, from the database to login to the HTML.
The 2026 Stack Overflow Developer Survey asked developers: "Which web frameworks and web technologies have you done extensive development work in over the past year?"[4] The table below shows the answers for the application and backend frameworks. A backend is the part of an application that runs on the server, not in the browser. ASP.NET Core is Microsoft's web framework for the C# language (ASP stands for Active Server Pages).
| Rank | Framework | Language | Share of people who answered |
|---|---|---|---|
| 1 | Next.js | JavaScript / TypeScript | 20.6% |
| 2 | ASP.NET Core | C# | 19.9% |
| 3 | FastAPI | Python | 18.5% |
| 4 | Express | JavaScript / TypeScript | 15.6% |
| 5 | Spring Boot | Java / Kotlin | 12.8% |
| 6 | Flask | Python | 12.5% |
| 7 | Django | Python | 11.3% |
| 8 | Laravel | PHP | 7.6% |
| 9 | NestJS | TypeScript | 5.8% |
| 10 | Ruby on Rails | Ruby | 5.7% |
| 11 | SvelteKit | JavaScript / TypeScript | 5.6% |
| 12 | Nuxt | JavaScript / TypeScript | 4.4% |
| 13 | Symfony | PHP | 4.0% |
| 14 | Phoenix | Elixir | 2.5% |
Two other frameworks in the same survey are worth knowing: Astro (6.2%) and Axum, for the Rust language (3.6%).
The table leaves out three kinds of entries from the survey's list: libraries for building screens (like React, 41.5%), runtimes that run JavaScript on a server (like Node.js, 46.4%), and content management systems like WordPress. The numbers show what the people who answered the survey use. They are not an exact measure of what the whole world uses, and they don't say that two frameworks with similar numbers are built in a similar way.
Not every framework solves the same amount of the problem
Frameworks come in three broad styles. The difference between them is how many decisions the framework makes for you.
- Full-stack, integrated frameworks give you many building blocks and many rules. Next.js, Django, Rails, Laravel and Nuxt are examples.
- Backend or API frameworks focus on the server side of the application. FastAPI, Spring Boot, ASP.NET Core and NestJS are examples.
- Minimal frameworks give you a small base, and you choose the other tools yourself. Express and Flask are examples.
Read the diagram from left to right: the further right, the more the framework decides for you.
These groups are not strict. Spring Boot can send finished HTML pages, and Django can serve an API without any pages. Rails makes many decisions at the start. Express gives you more freedom, but also more work and more things to get right. Neither approach is better in every case.
What happens when someone opens a product page
Before we look at the problems one by one, let's follow a single request.
A customer opens the online store and visits https://shop.example.com/products/42. The browser sends a request (a message asking for that page), and the server sends back a response (the page, or an error). Between those two moments, many parts of the system do a small job each.
This is a picture of the ideas, not a fixed order. For example, the login check can happen before routing, the cache can sit in several places, and a saved response can be sent back without the application running at all.
Still, almost every job a web application has appears in this one picture. A framework has to do each job, connect to a tool that does it, or leave you room to do it yourself. We can sort those jobs into eight areas.
The eight areas every web developer should understand
The eight areas follow the path of the request above, from the network to running the application for real users.
Each area below starts with the problem, then lists the ideas that belong to it in a table. Every table has the same shape: the idea on the left, and the problem it solves on the right.
HTTP and networking: how computers talk
Before a framework can do anything, the browser and the server must be able to talk to each other.
HTTP (Hypertext Transfer Protocol) is the set of rules for that conversation. It defines what a request and a response look like. The table also uses three other short names. DNS (Domain Name System) turns a name like shop.example.com into the number of a computer on the network. TLS (Transport Layer Security) encrypts the conversation, and it is the "S" in HTTPS. A CDN (content delivery network) is a group of servers spread around the world that keep copies of your content close to the users.
| Idea | The problem it solves |
|---|---|
| HTTP methods | Tell the server whether you want to read, create, change or delete something |
| URLs and query parameters | Name the thing you want, and say which part of it you want |
| Request and response headers | Carry extra information, such as the type of content or how long it can be cached |
| Status codes | Say whether the request worked, failed, or moved somewhere else |
| Cookies | Let the browser carry small pieces of information from one request to the next |
| DNS | Turn a domain name into a network address |
| TLS | Encrypt the data between the browser and the server |
| Connections and timeouts | Limit how long a request may take |
| Proxies | Pass requests on, and maybe check, change or redirect them on the way |
| Reverse proxies | Sit in front of the application servers and manage the incoming traffic |
| Load balancing | Share the requests between several servers |
| CDNs | Send content from a server close to the user |
None of these ideas belong to a framework: the browser doesn't care whether the server is written in Python or in Java. Everything you learn here works the same in every framework.
Handling requests: how the application answers
Once a request reaches the application, some code has to decide what to do with it.
The first step is routing: matching the URL and the HTTP method to a piece of code. Middleware is code that runs for many requests on their way in or out, for example to check a login or add a header. The rest of the table follows the request deeper into the application. JSON (JavaScript Object Notation), in the table, is the text format most APIs use to send data.
| Idea | The problem it solves |
|---|---|
| Routing | Match the URL and the method of a request to the code that handles it |
| Route parameters | Take values out of the URL, such as the product number 42 |
| Middleware | Run shared code for many requests |
| Request validation | Check that the incoming data has the expected shape and limits |
| Controllers and handlers | Organise the answer to one kind of request |
| Services and use cases | Hold the real business behaviour, such as "place an order" |
| API design | Decide how other programs talk to the application |
| Serialisation | Turn the application's data into a format like JSON |
| Redirects | Tell the browser to ask for a different URL |
| Rewrites | Send the request to different code without changing the URL the user sees |
| Error handling | Deal with failures and send back a useful answer |
| Rate limiting | Limit how often someone can make a request |
| Idempotency | Make sure that sending the same request twice doesn't do the work twice (for example, charging a card twice) |
| Background jobs | Do slow work outside the request, so the user doesn't wait for it |
| Webhooks | Receive messages from other systems when something happens there |
| Real-time communication | Push updates to the browser at once, with WebSockets or server-sent events |
For example, when a customer places an order, the request handler might check the shopping cart, check that the customer may place the order, ask the order service to do the work, and send back the result.
This example shows a useful rule: receiving a request and applying the business rules are two different jobs. Many frameworks make it easy to put both in the same function. Keeping them apart usually makes the application easier to test and to change.
Rendering: how users see the result
The application has the data. Now it has to turn that data into something a person can see. Next.js puts a lot of its effort into this area.
There are three main moments to build a page. With server-side rendering (SSR), the server builds the HTML when the request arrives. With client-side rendering (CSR), the browser builds or updates the page with JavaScript. With static site generation (SSG), the HTML is built once, before any request arrives, and then sent as a file. Hydration connects server rendering and browser rendering: the browser takes HTML that the server built and adds the JavaScript that makes the buttons and forms work.
The table also mentions CSS (Cascading Style Sheets, the files that describe how a page looks) and SEO (search engine optimisation: helping search engines find and understand your pages).
| Idea | The problem it solves |
|---|---|
| Server-side rendering (SSR) | Build the HTML on the server for each request |
| Client-side rendering (CSR) | Build or update the page in the browser |
| Static site generation (SSG) | Build the HTML before any request arrives |
| Hydration | Add interactive behaviour to HTML that the server built |
| Streaming | Send the page in parts, instead of waiting until all of it is ready |
| Templates and components | Reuse pieces of the page |
| Layouts | Share the same frame (header, menu, footer) between pages |
| Asset management | Handle the JavaScript, CSS, fonts and images |
| Bundling and compiling | Prepare the code so the browser can run it |
| Code splitting | Send only the code that the current page needs |
| Prefetching | Load the next likely page before the user clicks |
| Pagination and infinite scrolling | Show a long list in smaller parts |
| Accessibility | Make the page usable by people with different abilities |
| Localisation and internationalisation | Support several languages and regional formats, such as dates and prices |
| SEO | Help search engines find and understand the pages |
| Metadata and sitemaps | Describe the pages and list them for search engines |
| Progressive enhancement | Keep the main features working when the extra JavaScript doesn't load |
Not every framework renders pages itself. FastAPI, for example, is often used only to send JSON to a separate frontend (the part that runs in the browser). Rails, Django and Laravel usually build HTML on the server with templates. The job is the same. Only who does it changes: sometimes one framework, sometimes two technologies together.
Data and storage: how information is kept
Most applications need to remember things. A shopping cart, an order or a customer's profile must still exist after the request is over.
Most of the ideas here are about a database, a program that stores data and finds it again quickly. A relational database stores data in tables that point at each other, and you talk to it in SQL (Structured Query Language). An ORM (object-relational mapping) lets you work with those tables as normal objects in your programming language, instead of writing SQL by hand.
| Idea | The problem it solves |
|---|---|
| Databases | Store information and find it again later |
| Data models | Describe the things the application works with, and how they relate |
| Schemas | Fix the shape and the rules of the stored data |
| ORM | Work with database tables as objects in your language |
| Queries | Read or change specific data |
| Indexes | Make some searches in the database faster |
| Relations | Connect things, such as a customer and their orders |
| Migrations | Change the shape of the database safely as the needs change |
| Transactions | Make a group of changes succeed together or fail together |
| Connection pooling | Reuse database connections instead of opening a new one each time |
| Concurrency control | Stop two changes at the same moment from breaking the data |
| Data consistency | Decide when other readers see a change |
| Replication | Keep copies of the data, so it survives failures or serves more readers |
| Backups and recovery | Get the data back after a mistake or a failure |
| Object storage | Store files such as images, documents and videos |
| Search indexing | Make text search fast |
| Data retention | Decide how long information is kept |
One distinction here is easy to miss: the way the business thinks about an order is not always the way the database stores it. The application might treat an Order as one thing with a customer, a list of products and a total price. The database might split that same order across three tables.
The business model says what an order means. The storage model says how it is saved. When you keep the two apart in your head, it becomes much easier to change the database, the ORM or even the whole backend framework.
Caching: how to avoid repeated work
Suppose your store gets 10,000 requests for the same product page. Should the application ask the database 10,000 times? Usually not.
Caching means keeping a result you already computed, so the next request can reuse it instead of doing the work again. But caching brings a new problem: how do you know when the saved result is out of date? A saved result that is out of date is called stale.
This diagram shows one cache. A real application usually has several caches at once, and each one has its own rules about freshness.
The table uses TTL (time to live): the length of time a saved result may be reused before it counts as stale.
| Idea | The problem it solves |
|---|---|
| Browser cache | Reuse files on the user's own device |
| CDN cache | Reuse responses from a server close to the user |
| Server cache | Avoid repeating slow work on the server |
| Database query cache | Avoid running the same database query again |
| In-memory cache | Get often-used data very fast from memory, for example with Redis |
| Cache keys | Name each saved result, so the right one is reused |
| Cache invalidation | Remove or mark results that can't be trusted any more |
| Revalidation | Check, or refresh, whether a saved result is still current |
| TTL | Limit how long a saved result stays valid |
| Stale-while-revalidate | Send the old result now, and refresh it in the background |
| Cache consistency | Keep the saved data close enough to the real data |
| Cache stampedes | Stop many requests at the same moment from rebuilding the same result |
| Compression | Make the data smaller before sending it |
| Lazy loading | Wait to do work until it is really needed |
| Performance budgets | Set limits for speed, page size and resource use |
A common mistake is to think of caching as one feature. In practice, caching is several layers, and each layer has its own rules about when its data goes stale. One application might keep pages at the CDN, API results on the server, database results in memory and images in the browser.
A product's price can be out of date in any of these layers. That is why the customer in the introduction could still see the old price after you changed it.
Security and identity: how to protect users
Security is not one feature you install once and forget. It is many protections spread across the whole application.
Two words matter most here. Authentication checks who a user is. Authorisation checks what that user is allowed to do. The table also names several common attacks and standards:
- CSRF (cross-site request forgery): another website tricks the user's browser into sending a request to your site, for example to change their email address.
- XSS (cross-site scripting): an attacker gets their own JavaScript to run inside your page, in other users' browsers.
- SQL injection: user input changes the meaning of a database query.
- CORS (cross-origin resource sharing): browser rules that decide which other websites may read the responses from your server.
- OAuth 2.0 (Open Authorization): a standard that lets one system give another system limited access, such as letting a printing service read your photos.
- OpenID Connect: a layer on top of OAuth 2.0 that also tells the application who the user is. "Sign in with Google" uses it.
| Idea | The problem it solves |
|---|---|
| Authentication | Check who the user is |
| Authorisation | Decide what the user may do |
| Sessions | Remember that a user is logged in, from one request to the next |
| Tokens | Carry proof of identity or of permission |
| Password hashing | Store a check value for each password, never the password itself |
| OAuth 2.0 | Let one system give another limited access |
| OpenID Connect | Add "who is this user" on top of OAuth 2.0 |
| CSRF protection | Block actions that another site starts without the user knowing |
| XSS protection | Stop attackers' scripts from running in users' browsers |
| SQL injection protection | Stop input from changing a database query |
| CORS | Control which other websites may read your responses in the browser |
| Security headers | Turn on the browser's own security rules |
| Input validation and output encoding | Limit what untrusted input may contain, and show output safely |
| Secrets management | Protect passwords, keys and tokens that the application uses |
| Encryption | Protect data while it travels and, where needed, while it is stored |
| Access logs | Record sensitive actions and security events |
| Abuse protection | Slow down password guessing, bots and very large amounts of traffic |
| Dependency security | Track and fix security holes in the libraries you use |
| Privacy and compliance | Handle personal data as the law requires |
A framework may give you tools for authentication. Your application still has to apply its own access rules, because only your application knows that order 1234 belongs to Bob.
Architecture: how the code is organised
As an application grows, the way you organise the code becomes as important as the code itself.
Without clear borders, database queries, screen logic, business rules and calls to other services get mixed together. Then a small change in one place breaks something in another. Architecture is the set of decisions about where each kind of code lives. The table uses three short names. MVC (model–view–controller) splits code into the data, the screen and the code that connects them. DDD (domain-driven design) organises the code around the business itself. A BFF (backend for frontend) is a small server built for one specific frontend, such as one mobile app.
| Idea | The problem it solves |
|---|---|
| Layers | Separate the screen, the business behaviour and the technical tools |
| Separation of concerns | Stop unrelated jobs from depending on each other |
| MVC | Organise code into data, screens and the code between them |
| Domain modelling | Describe the real-world things and rules of the business |
| DDD | Build the software around the business domain |
| Dependency injection | Give a piece of code the tools it needs, instead of letting it build them itself |
| Repositories | Hide how data is stored behind a simple interface |
| Service layers | Group the operations the application can do |
| Ports and adapters | Keep the core logic separate from databases, payment services and other outside tools |
| Modular architecture | Split the application into areas that each do one thing |
| Monoliths | Deploy all the parts of the application together |
| Microservices | Split the parts into separate services that are deployed on their own |
| Event-driven architecture | Let parts react to messages ("an order was placed") instead of calling each other |
| BFF | Give one frontend a server made for its needs |
| API contracts and versions | Keep systems working together when one of them changes |
| Configuration management | Change settings for development, testing and production |
For example, placing an order could pass through these layers:
This is a choice you make, not a rule that every framework forces on you. A small application might not need every layer, and a large one can gain a lot from them. The goal is not to add as many layers as possible: it is to change one part of the application without breaking the parts that have nothing to do with it.
Operations: how the application keeps working for real users
An application that works on a developer's laptop is not yet ready for real users. It needs a reliable place to run, to receive traffic, to store data and to recover from failures. This is the area of production: the live system that real customers use.
The table uses CI/CD (continuous integration and continuous delivery): automatic steps that test, build and release the software every time the code changes. Observability means being able to see what the application is doing inside, from its logs, its numbers and its traces.
| Idea | The problem it solves |
|---|---|
| Web and application servers | Run the code and answer the requests |
| Serverless functions | Run code without managing the servers yourself |
| Edge computing | Run some code on servers close to the users |
| Containers | Package the application together with everything it needs to run |
| CI/CD | Test, build and release the software automatically |
| Environment configuration | Use different settings for development, testing and production |
| Horizontal scaling | Add more copies of the application to handle more traffic |
| Vertical scaling | Give one copy more memory or processing power |
| Stateless design | Let any copy of the application handle any request |
| Queues and workers | Process slow tasks reliably, outside the request |
| Scheduling | Run work at fixed times, such as every night |
| Observability | Understand what the application is doing inside |
| Logging | Record events and details for finding problems |
| Metrics | Measure speed, errors, resource use and business activity |
| Distributed tracing | Follow one request across several services |
| Health checks | Check whether a copy of the application can take traffic |
| Retries with backoff | Try again after a short failure, waiting longer each time |
| Circuit breakers | Stop calling a service that keeps failing |
| Graceful degradation | Keep the most important features working when one part fails |
| Disaster recovery | Bring the service back after a major incident |
| Feature flags | Turn features on or off without a new release |
| Rollbacks | Go back to the last version that worked |
| Testing strategies | Check that the code is correct at the unit, integration and end-to-end levels |
The framework solves only a small part of this area by itself. Next.js gives you a way to build the application and put it online. How reliable the live system is depends on all the parts together: your hosting provider, your database, your CDN, your monitoring tools and the way you organised the code.
The same ideas in different frameworks
Now we can come back to the idea from the start: the jobs stay the same, and only the tools change.
Suppose you have worked with Next.js for five years and you decide to learn Django. The code will look different, the rules will be different and the libraries will be different. But most of the jobs will be familiar, because you have already done them all in Next.js.
Here is the same job, "show product 42", in both frameworks. In Next.js, the folder name [id] is the route parameter, and the page reads it:
// app/products/[id]/page.tsx
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id); // your own function that reads the database
return <h1>{product.name}</h1>;
}
In Django, a list of URL patterns does the routing, and a view function does the work:
# shop/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("products/<int:product_id>/", views.product_detail),
]
# shop/views.py
from django.shortcuts import get_object_or_404, render
from .models import Product
def product_detail(request, product_id):
product = get_object_or_404(Product, pk=product_id)
return render(request, "shop/product_detail.html", {"product": product})
The two pieces of code share no syntax, but they do the same three jobs: take the number out of the URL, read the product from the database, and render a page. The table below lists more of these matches across four frameworks.
Some names in the table need a short explanation. Prisma and Drizzle are ORMs for TypeScript. Zod is a TypeScript library that checks the shape of data. JPA (Java Persistence API, now called Jakarta Persistence) is the standard way to map Java objects to database tables. Thymeleaf is a Java template engine for HTML. ERB (Embedded Ruby) is the Rails template format. Rack is the common interface between Ruby web servers and Ruby applications. Redis is a fast in-memory data store. Celery is a Python tool for background jobs. Flyway and Liquibase are tools that run database migrations.
| Job | Next.js | Django | Spring Boot | Rails |
|---|---|---|---|---|
| Routing | App Router | URL dispatcher | Request mappings | Rails Router |
| Handling a request | Route Handlers | Views | Controllers | Controllers |
| Middleware | Proxy, plus other layers | Middleware | Filters and interceptors | Rack middleware |
| Data models | Your own types, plus an ORM | Django models | JPA entities | Active Record |
| Database access | Prisma, Drizzle or plain SQL | Django ORM | Spring Data JPA | Active Record |
| Validation | Zod or your own code | Forms and serializers | Bean Validation | Active Model validations |
| Authentication | Outside libraries or services | Django auth | Spring Security | Built-in generator or libraries |
| HTML rendering | React on the server | Django templates | Thymeleaf | ERB views |
| Caching | Next.js cache, or Redis | Cache framework, or Redis | Spring Cache, or Redis | Rails cache |
| Background jobs | Outside queues and workers | Celery or similar | Spring scheduling, Spring Batch | Active Job |
| Database migrations | Prisma or a similar tool | Django migrations | Flyway or Liquibase | Active Record migrations |
These are typical tools, not exact one-to-one matches. Django middleware and Next.js Proxy don't have the same abilities, and they don't run in the same place. Django models do several jobs at once, while a Next.js application might keep its business model and its storage model apart. The ideas still carry over.
What carries over when you change language
When you move to a new framework, some of your knowledge comes with you and some doesn't. The table sorts the main kinds of knowledge into the two groups.
| Knowledge | Carries over to a new framework? |
|---|---|
| HTTP and how the web works | |
| Routing as an idea | |
| Security principles | |
| Data modelling and transactions | |
| Caching and freshness | |
| Architecture | |
| How deployment works | |
| The framework's rules and habits | |
| The language's syntax and behaviour | |
| The framework's functions and libraries | |
| Tools and deployment details |
Most of the "Yes" rows are the eight areas from this post. Take dependency injection as an example. Once you understand why it exists, you recognise it in Spring Boot, in NestJS, or in a TypeScript application you wrote by hand. The way you write it changes. The reason you use it doesn't.
Every framework decision has a cost
A framework is not just a list of features. It is a set of decisions, and every decision costs something. Three examples show the pattern.
Caching makes responses faster, but old data can be dangerous. A news article can be a minute out of date without harm. A bank balance can't. Server rendering sends useful HTML early, but it uses server resources for every request. Browser rendering allows very interactive pages, but the browser may have to download more code and data before the page is useful. A high-level ORM saves you from writing the same database code again and again, but it can hide a slow query behind a simple-looking line of code.
| Decision | What you gain | What it costs |
|---|---|---|
| Cache more | Faster responses, less load on the database | Users may see stale data |
| Render on the server | Useful HTML arrives early | Server work for every request |
| Render in the browser | Very interactive pages | More code to download before the page is useful |
| Use a high-level ORM | Less repeated database code | Slow queries are harder to spot |
An experienced developer doesn't memorise which features exist: they understand which problem each feature solves, what it costs, and when to use it. That is why knowing a framework's functions is not the same as understanding the system.
What to learn, in order
If you want to build this understanding step by step, follow the same path the request takes. Each step builds on the one before it.
- The base of the web: HTTP, DNS, TLS, browsers, servers and proxies.
- Handling requests: routing, middleware, headers, status codes, redirects and rewrites.
- Rendering: server rendering, browser rendering, static pages, hydration and streaming.
- Working with data: databases, models, schemas, queries, ORMs and migrations.
- Changing data safely: validation, transactions, changes at the same moment, and consistency.
- Caching: cache layers, invalidation, stale data and speed.
- Identity and security: authentication, authorisation, sessions, tokens and common attacks.
- Architecture: layers, domain models, services, repositories and dependencies.
- Communication between systems: APIs, background jobs, queues, webhooks and real-time updates.
- Production infrastructure: deployment, edge computing, scaling and reliability.
- Running the application: testing, logging, monitoring, tracing and recovering from incidents.
- Putting it all together: build the same application in Next.js, then compare it with another framework.
For each topic, ask the same four questions:
- What problem are we solving?
- Why does this problem exist?
- What are the common solutions, and what does each one cost?
- How do Next.js and other frameworks solve it?
These questions stop the learning from turning into a list of one framework's functions.
Frameworks change, the problems stay
A developer who only knows how to use Next.js can build applications with Next.js. A developer who understands HTTP, routing, rendering, caching, data, security, architecture and operations can reason about any web application, in any language.
Frameworks change. Functions get new names, as Middleware became Proxy in Next.js 16. Libraries get replaced. The problems underneath change much more slowly, so learning them is the part of your knowledge that lasts.
References
- Next.js, React Foundations: About React and Next.js
- Next.js documentation, Proxy
- Next.js documentation, How to use Next.js as a backend for your frontend
- Stack Overflow, 2026 Developer Survey: Web frameworks and technologies
Further reading: MDN (Mozilla Developer Network), HTTP for the networking area, and The Twelve-Factor App for the operations area.
