A comparison of Blazor and Vue when it comes to modern web development—a review of the pros and cons. Does Blazor stack up?
C# running in the browser you say?
Blazor WebAssembly has been released and brings an alternative to JavaScript for building modern, responsive web applications that run in the browser.
But how does it stack up compared to other, more established options for building "modern" web applications?
In this post we'll compare Blazor and Vue.js.
Note: Interested in how Blazor stacks up against React or Angular instead? Check out a comparison of Blazor and React here and a comparison Blazor of Angular here.
Specifically, we'll look at these key areas (for both Blazor and Vue):
Before we dig in, it's worth noting this article focuses on Blazor WASM, which runs in the browser using WebAssembly. That said, many of the points are equally valid if you're looking at using Blazor Server instead.
Explore Blazor: Free quick start guide to productivity with Blazor. Get our Beginner's Guide ebook
Vue is a JavaScript framework.
Vue prides itself on being "incrementally adoptable."
In its simplest mode, you can simply include the core Vue scripts in your application then set about building your components.
Beyond that, and for more complex applications, you can use Vue's own CLI to create (and ultimately publish) a Vue project.
As with most other JavaScript frameworks, Vue apps are built as a series of small components that you can then compose together to make bigger features (and ultimately entire applications).
You'll typically write your Vue applications using HTML, CSS and JavaScript (or TypeScript).
Blazor is a framework which also enables you to build client web applications that run in the browser, but using C# instead of JavaScript.
When you create a new Blazor app, it arrives with a few carefully selected packages (the essentials needed to make everything work) and you can install additional packages using NuGet.
From here, you build your app as a series of components, using the Razor markup language, with your UI logic written using C#.
To publish your app, you can use dot net's built in publish
command, which bundles up your application into a number of files (HTML, CSS, JavaScript and DLLs) which can then be published to any web server that can serve static files.
When a user accesses your Blazor WASM application, a Blazor JavaScript file takes over, which downloads the .NET runtime, your application and its dependencies before running your app using WebAssembly.
Blazor then takes care of updating the DOM, rendering elements and forwarding events (such as button clicks) to your application code.
There are two primary ways to start using Vue.
First, you can simply reference the scripts (via a CDN) and start adding components to any HTML page in an existing app.
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
Alternatively, you can install the Vue CLI:
npm install -g @vue/cli
Then create and launch a new project:
vue create hello-world
cd hello-world
npm run serve
When you use vue create
you'll be given various presets to choose from or you can pick and choose from options like enabling support for TypeScript, unit tests, etc.
For Blazor, you can use Visual Studio or spin up a new project via the command prompt.
dotnet new blazorwasm
cd blazorwasm
dotnet run
You have a few other options, like the ability to include infrastructure for authenticating users, and whether to host your Blazor app in an ASP.NET web application, but the command above is the simplest option to get started.
Vue is all about templates. Here's an example:
<div id="app">
<label>What's your name?
<input v-model="name" placeholder="Your name..."/>
</label>
<span>Hello {{ name }}</span>
</div>
<script src="https://unpkg.com/vue"></script>
<script>
var app = new Vue({
el: '#app',
data: {
name: ''
}
})
</script>
The template is contained within our #app
div.
We then create a new Vue app and tell it to use the #app
div as its target element.
v-model
sets up a binding between the text input and name
data property.
As a result, name
will always reflect what the user enters into the text input, and if the value of name
is changed programmatically this will be reflected in the text input.
We've used the {{ name }}
syntax to render the current value of name
so we can see it change instantly as we type new values into the text input.
This will get you up and running, but in practice most applications will be made up of several components, composed together to make bigger features.
To turn this greeting into a reusable component, we need a slightly different syntax.
Vue.component('greeting', {
data: function () {
return {
name: ''
}
},
template: `
<div>
<label>What's your name?
<input v-model="name" placeholder="Your name..."/>
</label>
<span>Hello {{ name }}</span>
</div>
`
})
There are some subtle differences between a Vue component and the app we started with:
template
attributedata
in a component is expressed as a function which returns an objectWith these changes we can now render this component wherever we like in our application.
<div id="app">
<greeting/>
</div>
In summary, a Vue application:
Blazor also encourages you to break your UI down into a number of smaller components.
Unlike Vue, you write your components using Razor and C#.
<label>What's your name?</label>
<input type="text" @bind-value="Name" @bind-value:event="oninput" placeholder="Bob"/>
<span>Hello @Name</span>
@code {
public string Name { get; set; }
}
We've got roughly the same markup, but this time we have used Blazor's @bind
syntax to bind our input to a property called Name
.
When the user enters their name, the Name
property will be updated with the value they enter.
By default Blazor would update the value of Name
on blur (when we clicked out of the text input), so we've added @bind-value:event="oninput"
to make it update the property as soon as we start typing.
You can now render this component wherever you like in your application...
<h1>
A brief introduction to Blazor...
</h1>
<Greeting />
In summary, a Blazor UI:
We've already seen one way Vue can handle data, storing name
directly in our greeting component.
var app = new Vue({
el: '#app',
data: {
name: ''
}
})
The other common option is to pass data in to a component.
Say we wanted to pass a headline to our greeting component:
<greeting headline="Welcome, thanks for being here!" />
Vue enables this via something called props.
Vue.component('greeting', {
data: function () {
return {
name: ''
}
},
props: ['headline'],
template: `
<div>
<h2>{{ headline }}</h2>
<label>What's your name?
<input v-model="name" placeholder="Your name..."/>
</label>
<span>Hello {{ name }}</span>
</div>
`
})
We've added a props array to the component:
props: ['headline'],
This makes our component accept a headline
value which we then render using standard interpolation syntax <h2>{{ headline }}</h2>
.
Props are the key to unlocking reusable components, making it possible to use the same component in lots of different scenarios, passing different values in each time.
While using data
and props
works well for many scenarios, you may run into a need for more centralized state in your applications.
One option is to roll your own data "store," whereby you have a central "store" object which is then shared between multiple components.
Alternatively, once you've set off down that road you can just keep on walking until you get to Vuex!
Vuex offers a Vue implementation of the Flux pattern for state management (you may have heard of this other Flux implementation called Redux).
Crucially, as with everything, it pays to keep to the simplest possible solution that meets the needs of your specific application, but it's good to know more advanced options are out there if you need them.
Broadly speaking, Blazor has the same two primary options for managing state.
You can store data in the component itself using properties (as with Name
in our example) or take data in via parameters (as with Headline
).
<h2>@Headline</h2>
<label>What's your name?</label>
<input type="text" @bind-value="Name" @bind-value:event="oninput" placeholder="Bob"/>
<span>Hello @Name</span>
@code {
[Parameter]
public string Headline { get; set; }
public string Name { get; set; }
}
As with the Vue example, when you render Greeting
you can pass in a headline and it will be rendered accordingly.
<Greeting Headline="Welcome, it's still great to see you..."/>
For more advanced scenarios, much like Vue you can roll your own centralized data store for Blazor apps or check out emerging options for using the Flux pattern with Blazor via projects such as Fluxor.
We've already seen the core mechanism Vue employs for handling forms: the v-model
directive.
You can use v-model
to bind most form inputs to data, including text inputs, selects, checkboxes, etc.
Here's the markup for a simple contact form.
<div id="app">
<form @submit.prevent="submit">
<label>
Name:
<input type="text" v-model="name"/>
</label>
<label>
Thoughts?:
<input type="text" v-model="comments"/>
</label>
<input type="submit" value="Submit"/>
</form>
</div>
v-model
does the heavy lifting: keeping your component's data in sync with the values entered by the users.
We direct this form to a submit
method in the Vue component using @submit
and the optional prevent
qualifier to prevent the default browser submit behavior.
Here's the corresponding JavaScript.
<script>
var app = new Vue({
el: '#app',
data: {
name: '',
comments: ''
},
methods: {
submit: function(e){
// submit data to API
console.log(`${this.name}:${this.comments}`);
}
}
})
</script>
The submit
method will be invoked when the user submits the form, and there we log out the values for name
and comments
.
For validation, you'd either need to write your own logic in the submit
method, or lean on unofficial Vue libraries such as vuelidate and VeeValidate.
Blazor has built-in functionality to handle your form data and validation using something called EditForm
.
Here's the markup for an equivalent contact form.
@using System.ComponentModel.DataAnnotations
<EditForm Model="FormModel" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<label>
Name:
<InputText id="name" @bind-Value="FormModel.Name"/>
</label>
<label>
Thoughts?:
<InputText id="comments" @bind-Value="FormModel.Comments"/>
</label>
<input type="submit" value="Submit"/>
<ValidationSummary />
</EditForm>
We've replaced the standard HTML input
elements with Blazor's InputText
component.
Technically it's entirely possible to use standard HTML elements and forms with Blazor, but using the built-in controls makes certain things a lot easier (validation being one, as we'll see in a moment).
Where Vue used v-model
, we have @bind-value
, which similarly takes care of both reading the value from a field and updating it when a user types in a new value.
The form itself is based on a model of our choosing (where the form values will live), and we've told it which method to invoke when the form is submitted (and is valid).
Note how we've included a DataAnnotationsValidator
and ValidationSummary
; these components wire up the form to automatically respect any validation rules we set up on our model.
Here's the rest of the code:
@code {
protected ContactUsModel FormModel { get; set; } = new ContactUsModel();
async Task HandleValidSubmit()
{
// post to your API
Console.WriteLine($"{FormModel.Name}: {FormModel.Comments}");
}
protected class ContactUsModel
{
[Required]
public string Name { get; set; }
public string Comments { get; set; }
}
}
The ContactUsModel
class could live anywhere in our project.
We have a FormModel
property and HandleValidSubmit
method.
When someone fills in the form, if they've met the validation rules (a Name
has been entered) then HandleValidSubmit
will be invoked.
Otherwise, the ValidationSummary
will be used to show which fields have validation errors.
Overall the Vue and Blazor form implementations share a lot of similarities:
Where Blazor stands apart is in its built-in validation support, using the well-established DataAnnotations
library and some new Blazor helper components.
Vue offers a separate router you can plug in to your application.
You can include it in your HTML page:
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
You're then able to render a router-view
in your markup.
<router-view></router-view>
This is where Vue will render content as you move between routes.
You can configure routes in the JavaScript for your app.
<script>
const Home = { template: '<div>Home</div>' }
const Contact = { template: '<div>Contact Us</div>' }
const routes = [
{ path: '/home', component: Home },
{ path: '/contact', component: Contact }
]
const router = new VueRouter({
routes: routes
})
const app = new Vue({
router
}).$mount('#app');
</script>
Here we have two components (Home
and Contact
).
Then we've declared two routes pointing to these components.
Next, we declare a router and assign our routes to it.
Finally, we create a new Vue app, using the router.
With all this in place, you can now navigate to these two components using the #
symbol.
You will often need to pass further data along in the route. For example, if you're routing to a details page for a product, you would expect to provide a product id in the route...
You can configure your routes to accept a parameter:
routes: [
{ path: '/product/:id', component: User }
]
You can then retrieve this id using $route.params
.
<h2>
Displaying product details for {{ $route.params.id }}
</h2>
With a little more plumbing you could also capture these route parameters via your component's props and avoid having to use $route.params
everywhere in your components.
Blazor includes routing "out of the box". If you want to make a component "routable" you can simply add a @page
directive...
@page "/GreetMe"
<h1>
Welcome!
</h1>
Now any request to http://<your-web-site-here>/GreetMe
will render this component.
You can also pass data in via the route and capture it in a parameter, like this:
@page "/GreetMe/{Name}"
<h1>
Welcome @Name!
</h1>
@code {
[Parameter]
public string Name { get; set; }
}
Now any request to http://<your-web-site-here>/GreetMe/Jon
will render a more personalized greeting (well, if your name's Jon according to this example!)
Chances are your web application will need to fetch data from an API at some point.
Vue remains agnostic on how you approach this, leaving you free to use the native fetch
API or any of the many third-party libraries, such as "Axios."
The key is knowing when to make the call, and for this Vue offers a mount
lifecycle hook.
<script>
new Vue({
el: '#app',
data(){
return {
tickets: null;
}
},
mounted(){
axios
.get('api/Tickets')
.then(response => (this.tickets = response));
}
})
</script>
Now when this component is mounted:
api/Tickets
tickets
Once we have the data, we can loop over it using Vue's v-for
directive and render markup for each item.
<div id="app">
<div v-for="ticket in tickets">
{{ ticket.title }}
</div>
</div>
With Blazor, you can use HttpClient
for all your data-fetching needs!
Under the hood, this defers to the native fetch
API, but you can generally just ignore that and use the abstraction.
Here's an example:
@using System.Net.Http
@inject HttpClient Http
@foreach(var ticket in _tickets){
<div>
@ticket.Title
</div>
}
@code {
private Tickets[] _tickets;
protected override async Task OnInitializedAsync(){
_tickets = await Http.GetFromJsonAsync<TicketSummary>("api/Tickets");
}
}
OnInitializedAsync
is broadly equivalent to Vue's mounted()
lifecycle hook and will run when our component first loads.
Notice how we're able to use GetFromJsonAsync
, passing in a Type to automatically deserialize the results of the HTTP call into an instance of TicketSummary
? This is where Blazor has one significant advantage over JavaScript frameworks...
Because you're writing your web app in C#, you can use the same data models in your frontend and backend (API) code.
Let's say for example you need to retrieve a list of people...
The Person
model lives in a shared class library.
Both your Web API and Blazor Client projects reference this shared library.
Now your API can be strongly typed, returning (and accepting) data using the Person
model.
The data is still serialized and sent "over the wire" as JSON data, but your Blazor application can deserialize the JSON data using the exact same Person
model that was used to serialize it in the first place.
Now if you make breaking changes to your Person
model, you'll immediately see compilation errors if you've introduced any breaking changes which affect either the client or server use of the model.
Now that we've seen them both in action, which one should you choose?
Naturally it's hard to make direct comparisons and which one you prefer will largely depend on your own background, skills and preferences.
Saying that, we've seen a number of similarities but also a few key differences between the two.
Vue is lauded for its light touch compared to other frameworks.
You can easily add Vue to an existing application, opening the door to incrementally improving your app without rewriting the entire thing.
The Vue CLI then comes into play if you do decide to structure your entire app around Vue, abstracting away the complexities of setting up a JavaScript build environment.
Vue Pros
Vue Cons
Try Kendo UI for Vue—Complete UI component library for web apps. Free Trial
Blazor has the obvious distinction that it uses C# instead of JavaScript.
This offers several advantages if you're coming from a C# background.
You can stick to the ecosystem you already know (NuGet, the dotnet
tooling, Visual Studio or VS Code).
The ability to share models between client and backend API is a big deal and makes it much harder to inadvertently break your application.
Blazor Pros
Blazor Cons
One thing to call out here is the initial download time.
When someone accesses your Blazor WASM app for the first time their browser will download a version of the .NET framework as well as your application's files.
Once they have these files, they don't need to download them again, but it does mean you're likely to see a "loading..." indicator first time around.
The team at Microsoft has done a lot of work to get this initial download size down but naturally this means Blazor is better suited to some web applications than others.
You probably wouldn't want to use it for things like product landing pages where there's little to no business logic and it's imperative that the page loads as quickly as possible.
But, for any Line of Business apps, this initial download is unlikely to pose a major issue.
Try Telerik UI for Blazor—Native components for building web apps with C#. Free Trial
Where Blazor fits into your plans will largely depend on your existing experience and how you feel about JavaScript.
If you're comfortable with JavaScript and the ecosystem, Vue is a solid framework which can easily scale up or down as your application requires.
On the other hand, if you already know and enjoy using C#, and have generally found JavaScript (the language and ecosystem) difficult to learn and live with, Blazor WASM is potentially a game changer.
So, are you thinking of using Blazor? Is it a contender or your next project, or will you be sticking with Vue for now?
The choice is yours!
Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.