As a developer working with interactive maps embedded into complex web applications, I find the most interesting part of web development to be the ability to create instantaneous user feedback and smooth transitions of data between server and client environments. My tool of choice in this arena for the past five years has been ASP.NET Core Blazor, a powerful web framework that allows you to write both client and server code using C#, HTML, and CSS.
While Blazor is a robust and stable tool for building production applications, it does have a much smaller ecosystem of developers, open-source packages, and online documentation compared to something like React. Until frameworks like Blazor came out, JavaScript was the only way to create interactive and responsive web applications. That de facto status has led to the huge success of frameworks such as React.
Not wanting to live fully inside my .NET bubble, I decided to dive into React and learn about this JavaScript library and how it compares to Blazor. This blog post will document my findings, coming from the perspective of starting a new application in both Blazor and Next.js, the most popular React framework. If you are a JavaScript developer, you may find this a good starting point for learning about Blazor, or if you are a .NET developer like me, this may help you understand the broader web development ecosystem and how Blazor fits into it.
TL;DR:
- Blazor and React/Next.js both offer modern component-based web development.
- They each have a single unified language for client and server code, an easy starting point with simple templates, and a large ecosystem of packages and libraries to help you build your application.
- Blazor provides you with a “batteries included” template experience, giving you a full website with navigation and interactive components out of the box.
- Next.js starts with a minimal, “blank canvas” template, and lets you add the pieces you want.
Read on for the full hands-on comparison!
Installing the Frameworks and Creating a New Project
Both Next.js and Blazor are fully cross-platform and can be developed on any Mac, Linux, or Windows machine. I’m going to start with WinGet to be able to install easily on my Windows PC, but you can also use any other installation system or download packages from the web. Below is a comparison of the commands to install and create your first application.
.NET Blazor
Install
> winget install Microsoft.DotNet.SDK.9
List all the available templates
> dotnet new list
List all the options for this template
> dotnet new blazor -h
Create the application
> dotnet new blazor -o HelloWorld -int Auto
Open in VS Code
> cd HelloWorld
> code .
Before creating a project, I’m calling list
to see all of the available templates. This includes not just web applications, but console, desktop, and cross-platform mobile applications as well! Once I find the command for the blazor
template (there are other options which you can read about here), I similarly use -h
to see a list of template options. The -o
option lets you name the new project, otherwise, it gets a default name.
I chose -int Auto
, which sets the Interactive Render Mode. Blazor was originally created to move ASP.NET Core from a server-side, non-interactive model to a fully interactive, client-side model that could compete with SPA libraries like React. This is accomplished by running .NET on top of WebAssembly in the browser. Blazor also provides a second option for running interactive components on the server, where SignalR is used to send events between client and server in real time. Interactive Auto
means that the first time you visit a page, it will connect with the SignalR server-rendered components and load the WebAssembly data in the background. Then, on repeated visits or refreshes, it will use the WebAssembly version. This is a great way to get the best of both worlds, fast first load times and snappy interactive performance.
After creating the project, we change the directory (cd
) into the new folder, and use code .
to open that folder in Visual Studio Code. Of course, any IDE or even text editor will work for Blazor and Next.js, but I like VS Code for small, quick projects due to its fast startup and great extensions.
Next.js React
Install
> winget install OpenJS.NodeJS
Create the application
> npx create-next-app@latest
The following prompts appear in sequence:
Need to install the following packages:
create-next-app@15.3.1
OK to proceed? (y)
What is your project named? hello.world
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*
Open in VS Code
> cd hello.world
> code .
I’m using Node.js and the bundled npm to install and manage my JavaScript ecosystem. I started with the Next.js Getting Started docs to find the command to create a new project, which is create-next-app@latest
. Unlike with the Blazor project, I am prompted here to answer a group of questions that help determine the contents of my template. This is my first React app, but I’m no stranger to the JavaScript ecosystem (GeoBlazor is a wrapper around a JS library with thousands of TypeScript files). My template choices were:
- Project Name - I first tried a name similar to how I would name a .NET project:
Hello.React.World
. This gives you an error,Invalid project name: name can no longer contain capital letters
. Not sure why that limitation exists, but no big deal, I did the same name with lowercase letters. It seems fine with periods and dashes, which still gives a lot of flexibility for naming. - TypeScript - Yes. Being mostly a C# developer, I strongly prefer strongly-typed languages.
- ESLint - Yes. I like relying on linting/formatting tools to keep my code clean and warn me if I’m doing something wrong.
- Tailwind CSS - No. In my opinion, modern vanilla CSS has all the features I need for styling, and in practice, it seems to be better encapsulated than Tailwind. Even on their landing page, you can see examples such as a button with eight different classes defined. To my eye, this usage really clutters up the HTML markup and goes against the reusability of components, scoped CSS, and custom classes. Yes, I know you can create custom classes with Tailwind to group the styles, but if I’m doing that, again, I’d rather just use CSS.
- App Router - I chose yes. Routing is apparently one of the many things in the JavaScript world that you need to choose from many options. The Next.js router is only one choice, there are also at least React Router and TanStack Router suggested on the React docs site. However, it appears that the Next.js router is the only one available directly from this wizard.
- Turbopack - Yes. This is a bundler, but I’m not sure what would happen if I chose
No
. I’m more familiar with esbuild, which I love for its speed and simplicity. Seems like with TypeScript, I need something to run the compiler, so this is fine for now. - Import Alias - I just hit enter. I found this blog post, which does a good job explaining the option.
Here’s a quick summary of my takeaways from the installation and new project processes:
Blazor | Next.js | |
---|---|---|
Pros | simple, one line to create a project | create command prompts you for important choices before building the template |
fast, no extra installs necessary | ||
concise list of template options directly in the CLI with dotnet new list |
||
Cons | templates and options are hidden behind the help menu, easy to forget, harder to discover | lots of questions to answer to create the project, had to research what several of them meant (e.g. turbopack, import alias) |
project name can’t contain capital letters? | ||
templates only viewable and available on GitHub, huge list |
While I’m used to the .NET ecosystem and CLI, I have definitely been frustrated by the inability to easily remember and select all the options for a Blazor template. Options like including authentication and interactive render modes would be much easier to decide upfront than change later, and I loved how the Next.js create-next-app
command prompted me for various options.
On the other hand, once you do know what you want, the conciseness of the .NET CLI and template can be a nice feature, and I was definitely up and running with the Blazor app more quickly than with the Next.js app. I also like the fact that I can see a short list of .NET template options with dotnet new list
.
Repository Overview
Once open in VS Code, I like to review the file tree and see how the project is organized.
Blazor File Structure | Next.js File Structure |
---|---|
|
|
First thing I notice here is that the Blazor repository is a much larger template. Part of this is due to my selecting -int Auto
, which added the secondary HelloBlazorWorld.Client
project for WebAssembly. The difference is also representative of the static-typed, business-oriented .NET ecosystem vs. the “freewheeling” JS approach.
Configuration & Project Files
Blazor Project File | Blazor App Settings |
---|---|
|
|
|
|
Next.js package.json | Next.js tsconfig.json |
---|---|
|
|
|
|
Next.js eslint.config.mjs | Next.js next.config.js |
---|---|
|
|
|
|
For those new to .NET, the .csproj
is the Project file, which you can probably tell is a flavor of XML. It’s pretty self-explanatory. Nullable
(Nullable reference types) and ExplicitUsings
are modern C# language features that are “opt-in” to avoid breaking older applications. Like JavaScript/ECMAScript, C# is constantly evolving and has added many new features over the past two decades, most of which are definitely worth adding to your workflow. You can also see in the project file a project reference from the client application to the server application, and a package reference to a NuGet package, the .NET equivalent of npm.
When I first saw this architecture, introduced in .NET 8, I was confused and not a fan. Why would the server application depend on the client application? But this is just a convenient way of saying that if you put code in the client project, it can be run both client and server side, while the client itself can be deployed independently.
The other important .NET configuration file is appsettings.json
. You can have an appsettings.Development.json
and appsettings.Production.json
that override the main file. These can also be overridden by User Secrets, and of course by environment variables in whatever deployment environment you use. The JSON schema of appsettings.json
is completely up to you, although there are a few things like logging that are pre-defined.
On the Next.js side, package.json is the configuration for npm. It defines our package dependencies as well as some scripts for ease of startup. tsconfig.json is the TypeScript configuration file, which sets rules as to how the TypeScript is parsed and transpiled to JavaScript. ESLint, which helps with keeping our code organized and clean, is controlled by the eslint.config.mjs. Finally, next.config.js is where we can add custom Next.js server settings such as base path, cross-site rules, and redirects. While this is a JavaScript module, it is not included in the client-side bundle and is only used at compile time by the server.
Having all these separate configs in the repository definitely makes the Next.js configuration seem more complex than Blazor. Of course, you can extend Blazor with TypeScript, in which case you have to deal with all of the above!
The closest parallel I can draw above is between .NET’s appsettings.json
and Next’s next.config.ts
. While JSON is a universal and easy-to-read configuration language, I really love how Next.js is laying out all the configuration in the same language as the program itself is written! Of course, you can certainly do this in C#, making your settings a static class, but it is not the normal or expected pattern.
UI Files
Blazor App.Razor | Blazor Routes.Razor |
---|---|
|
|
App.razor shows the root DOCTYPE, html, head, body, meta, links, and scripts tags, as well as the nested Routes razor component. | Routes.razor file shows the Router component that loads assemblies to find pages, loading from both the server and client program, and a Found, RouteView, and FocusOnNavigate component. RouteView loads the MainLayout component. |
Blazor MainLayout.Razor | Blazor NavMenu.Razor |
---|---|
|
|
MainLayout.razor shows the layout of the page, including the sidebar with the NavMenu component, a top row with a static About link, and an error handler. | NavMenu.razor shows the sidebar navigation menu, with NavLink components to the various pages in the application. |
Blazor Home.Razor |
---|
|
Home.razor demonstrates a Razor component page, with the @page declaration showing the route, the PageTitle component showing the title for the page in the browser, and markup for the page content. |
Here we can see more of the .NET attention to code and application structure. Not only are the UI files organized into Layout
, Pages
, and Components
folders, but the main page itself is broken down into App.razor
, which contains the HTML structure, head, and metadata, Routes.razor
, which details the functionality of the Blazor router, MainLayout.razor
, which gives the shared structural layout to be used across pages, NavMenu.razor
, which details the navigational side bar, and Home.razor
, which contains the page content. These are all examples of Razor components, the fundamental building block of Blazor. The @
symbol throughout the file identifies where C# code begins, and the PascalCase
markup tags are other nested Razor components, while lowercase tags are plain HTML. Code, components, and HTML can all be intermixed within a Razor file to encapsulate and generate our UI.
I didn’t include the other pages and components in the screenshots, but the template also includes Counter.razor
, which demonstrates user interaction on a button, and Weather.razor
, which shows a data grid loading weather data. Notice the @page "/"
line at the top of Home.razor
. Counter.razor
starts with @page "/counter"
. These are the route parameters, and also the only difference between a page and any other component. There is also an Error.razor
component for showing errors during development and a ReconnectModal.razor
component for showing while fixing interactive sessions that get disconnected.
Next.js layout.tsx | Next.js page.tsx |
---|---|
|
|
Layout.tsx shows the React function that generates the root html tag, with a metadata variable for the head and a RootLayout component. It takes a {children} inner content, which is the page. |
Page.tsx shows the React function that generates the home page content, with a header, image, and button. |
Next.js/React has a very different approach. The layout.tsx
exports a Metadata variable, which appears to feed the HTML <head>
. However, there’s definitely other content in the head, some of which appears to be the necessary loading scripts for React to function. The RootLayout
at the bottom of the file is our first React component. As you can see, the Markup-first approach of Razor is flipped upside down here, with the TypeScript code emitting the HTML. However, there are still similarities in how variables can be used to insert properties or child components.
In page.tsx
, we see a full-page React component, with curly braces {}
indicating TypeScript code injection. Like in Razor files, it appears that PascalCase represents child components; here we see <Image>
components laid out on the page.
Unlike in Blazor, the navigation route is not defined in the file, but by the folder structure. Since this page.tsx
is in the app
folder, it is the home route. If you put a page inside an app/counter
folder, then you would navigate to that page with the /counter
route. This pages routing approach is wild to me because every page is called page
, so the only thing differentiating them is the directory! I think this would drive me crazy in an IDE, where I typically use a keyboard shortcut and file name search to quickly navigate between files.
Static Assets
The approach to CSS and image files is quite similar between the two frameworks. The one difference as I mentioned above is the folder structure. In ASP.NET Core, wwwroot, signifies the “public” folder, and this contains global CSS, JavaScript scripts, and static images. In Next.js, this most closely corresponds to the public folder. However, only truly static files can live in this folder, because the CSS and TS files are typically imported into React components. Notice the line import styles from "./page.module.css";
at the top of page.tsx
. The .module.css
file is a “scoped” CSS file, meaning it will only impact that one component. Scoping is a very handy way to keep global CSS from causing conflicts between components.
Blazor also supports compiled and scoped CSS, which you can see in files like MainLayout.razor.css
and NavMenu.razor.css
. Unlike in Next.js, these are imported and applied automatically by the framework compiler, and don’t need to be declared in the Razor component.
Running the Application
Let's get these applications started!
> dotnet run
> npm run dev
.NET can implicitly find a project file in the current directory to run and defaults to debug/development mode, so the simple command dotnet run
is shorthand for dotnet run HelloBlazorWorld.csproj -c Debug
. To switch to production/release you would use -c Release
. For Next.js, the scripts dev
and start
correspond to development and release environments.
It was really simple, as a new React developer, to build and get started with Next.js. I was surprised by how simple the template was compared to Blazor templates. I had to learn to understand the pages and folder navigation, but once I had these basic concepts down, creating a basic application was very simple.
Startup for both applications was quite fast. Next.js reported ~800 milliseconds, while Blazor took a whopping 7-8 seconds. I will warn any developers new to .NET, that as your applications become larger, this build time does go up. However, .NET supports Hot Reload of file changes, (from the command line use dotnet watch run), which allows continuous incremental changes, for a development cycle more akin to what JS developers are used to. Even with hot reload, however, .NET, as a fully-compiled and managed environment, will likely never match the rapid development cycle of the JavaScript ecosystem. This is definitely a tradeoff worth considering alongside the significant runtime server performance gains of .NET over Next.js/Node as demonstrated in the TechEmpower Framework Benchmarks.
The differences in default templates for each framework are very noticeable on startup. ASP.NET Core begins with a multi-page template including sidebar navigation, top banner, and interactive page samples. This is great if you want or need such a framework, but otherwise might be code you just need to delete. There is an option -e
or --empty
that will remove most of the UI and give an empty template.
Next.js presents a very minimal single-page template with a few links, images, and buttons. There are other Next.js templates available in the Next.js Examples Repository, which you can create with npm create-next-app@latest --example [example-name]
. This is a large list of samples and would require a lot of study, picking and choosing from multiple templates to get the pieces that you really wanted. For example, there are examples with-cms
, with-stencil
, and with-stripe-typescript
. If you want all 3 in one project, you would have to combine them yourself. .NET on the other hand doesn’t even really have a repository like this. It does have more built-in options for things like authentication and database/ORM setup right in the template, but everything else is a NuGet package and you must find and follow the instructions for each library.
As mentioned above, if I run dotnet watch run
to get the .NET hot reload, then I can work in both environments pretty much seeing my changes in real-time, at least as long as I’m doing simple UI changes. I was able to change the page text, css, and markup in both without any issues.
Up to this point, both Blazor and Next.js appear to be very straightforward and capable frameworks for simple web development. In the next post of this series, we turn up the complexity a lot, by building a practical GIS web application using each framework. We’ll explore:
- Component architecture and data flow
- Interactive map integration
- State management approaches
- Server/client communication patterns
- Performance optimization techniques
- Development workflow and debugging tools
Whether you’re a .NET developer considering React, or a JavaScript developer curious about Blazor, you’ll see how these modern frameworks handle real-world challenges in building complex interactive applications.
And if you just can’t wait for the next post where we dive into building a full GIS web app, check out GeoBlazor — a powerful open-source library that lets you add interactive maps to your .NET Blazor applications today.