This page looks best with JavaScript enabled

Nuxt/Vue and Next/React - A Predicament

 ·  ☕ 9 min read

I have a full-time job that does not involve active web development. I am, however, on the constant look-out for the next big thing that will solve all my life’s problems.., by quickly creating apps that is.

Vue has always been a tool that I depend on. Vue is quite easy to understand, use and build apps with. Nuxt just makes it even easier.

I found React to be too verbose, generally more complicated, and not fun to work with. With NextJS 14, React Server Components, and the ** huge ** number of NextJS templates out there (not to mention v0.dev), I could kinda see how my workflow could be so much more easier.

The big question though was - “does Next+React really deliver the goods for me”. And thus began a long time “investing” effort to find out what should be my stack of choice for the foreseeable future.

tldr;
It does not matter. Stick to the tooling that you like.

Before we move forward, for those of you who want all the other libraries & frameworks considered - yes, I do code in them from time to time. No, they cannot be my go-to choice since I need to create things quickly lest I lose my interest and having lot of people out there simply means I can look forward to more answered questions and getting away with my scrappy “expertise”.

The Need

I am largely interested in building applications that help organize data, and provide ways to get things done in general. While not strictly focused on enterprises, they will border on things like -

  1. CRM/ERP’s of the world
  2. Project organization
  3. People and task management for specific focus areas like recruitment, vendor management, etc.

My typical apps have -

  1. Forms
  2. Data tables
  3. A few charts, reports and such

Indirectly, I need -

  1. Ability to switch b/w real-time and classic fetching of updates
  2. Easier state management
  3. Ability to create a lot of reusable components
  4. Ability to integrate with many libraries that can integrate well with documents, tables, reports and so forth

The Comparison Factors

Given the need is so generic and seem straight-forward at the outset, it is easy to get carried away. I decided to focus on a few seemingly random factors that I find useful or problematic.

  1. Component Structure
  2. Server-Side Rendering & Data Fetching
  3. State Management
  4. Performance
  5. Developer Experience

Component Structure

Components are building blocks of any app. They are the ones that make up the UI and the logic behind it.

Creating components in Vue has been a breeze. The single file component structure is a joy to work with.

  • Everything is neat and organized - from code, to styles, to the HTML
  • The separation of concerns is easy to manage
  • Code for reactive UI looks intuitive and easy to understand
  • Reusable code can be managed through Components and Services. Auto imports in Nuxt make it even easier to use them
1
2
3
4
5
6
7
8
9
<template>
  <div>
    <h1> {{ title }} </h1>
  </div>
</template>

<script setup>
const title = 'Hello World'
</script>

React is not far off from here -

1
2
3
4
5
6
7
export default function App() {
  return (
    <div className="App">
      <h1>Hello World</h1>
    </div>
  );
}

A somewhat more complexified version can be -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useState } from 'react';

function TodoList() {
  const [items, setItems] = useState([]);
  const [newItem, setNewItem] = useState('');

  const addItem = () => {
    setItems([...items, newItem]);
    setNewItem('');
  };

  return (
    <div>
      <input value={newItem} onChange={e => setNewItem(e.target.value)} />
      <button onClick={addItem}>Add</button>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

Vue still manages to look clean -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
  <div>
    <input v-model="newItem" />
    <button @click="addItem">Add</button>
    <ul>
      <li v-for="(item, index) in items" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';


const items = ref([]);
const newItem = ref('');

const addItem = () => {
    items.value.push(newItem.value);
    newItem.value = '';
};

</script>

I had a hard preference for Vue structure (think Options API), but not any more.

  • Reactivity is easy to write & manage in Vue. Just use ref / reactive and you are good to go.
  • Intuitive code in templates and script. Cleaner separation of concerns
  • Bidirectional data binding is a breeze
  • Template helpers like v-for, v-if are a God-send

Server-Side Rendering & Data Fetching

React Server Components make it easier to write server-side code and have the right mix of client and server logic in the same component.

For example, I could fetch posts data from server and send the HTML to the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { useEffect, useState } from 'react';

export default function PostList() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => setPosts(data));
  }, []);

  if (posts.length === 0) {
    return <div>Loading...</div>;
  }

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </li>
      ))}
    </ul>
  );
}


No haggling around with API calls, no moving around JS to get basic things done, and more control over how/who fetches data.

If I use Nuxt for the same problem, here’s how the component looks like -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script setup>

const { data: posts } = await useAsyncData(() => $fetch('https://jsonplaceholder.typicode.com/posts'))
</script>

<template>
    <div>
        <div v-for="post in posts" :key="post">
            {{ post }}
        </div>
    </div>
</template>

See Nuxt SSR.

RSC is a hard win for React. Next components are server by default, and mixing server / client components without a care for the world is enticing. While Nuxt makes it easy to build data fetching on top of Vue, it just does not compare to the magic of RSC.

That said, I really love the Vue way of doing things -

  • don’t worry about useEffect and other thousand hooks
  • no easy foot-guns causing performance issues or code spaghetti
  • clean, easy to understand template

Many other SSR and data fetching features exist, but are not super important for me.

State Management

Using state management in React can be tricky. There are many ways to do it, and each has its own pros and cons. Let us see an example of a simple todo app using Zustland.

The component will look something like this -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react';
import useStore from './store';

const TodoApp = () => {
  const todos = useStore(state => state.todos);
  const addTodo = useStore(state => state.addTodo);
  const toggleTodo = useStore(state => state.toggleTodo);
  const removeTodo = useStore(state => state.removeTodo);
  const [newTodo, setNewTodo] = React.useState('');

  const handleAddTodo = () => {
    addTodo(newTodo);
    setNewTodo('');
  };

  return (
    <div>
      <input value={newTodo} onChange={e => setNewTodo(e.target.value)} />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(index)} />
            {todo.text}
            <button onClick={() => removeTodo(index)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoApp;

.. and the store -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import create from 'zustand';

const useStore = create(set => ({
  todos: [],
  addTodo: (text) => set(state => ({ todos: [...state.todos, { text, completed: false }] })),
  removeTodo: (index) => set(state => {
    const newTodos = [...state.todos];
    newTodos.splice(index, 1);
    return { todos: newTodos };
  }),
}));

export default useStore;

In the Vue world, Pina (or even Nuxt state mgt.) is quite easy to use.
The component is simpler but comparable -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
  <div>
    <input v-model="newTodo" type="text">
    <button @click="addTodo">Add Todo</button>

    <ul>
      <li v-for="(todo, index) in todos" :key="index">
        {{ todo.text }}
        <button @click="removeTodo(index)">Remove</button>
      </li>
    </ul>
  </div>
</template>

<script>
import { ref } from 'vue';
import { useStore } from './store';

export default {
  setup() {
    const store = useStore();
    const newTodo = ref('');

    const addTodo = () => {
      store.addTodo(newTodo.value);
      newTodo.value = '';
    };

    return {
      todos: store.todos,
      newTodo,
      addTodo,
      removeTodo: store.removeTodo,
    };
  },
};
</script>

Here’s how the store looks like -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { defineStore } from 'pinia';

export const useStore = defineStore({
  id: 'todos',
  state: () => ({
    todos: [],
  }),
  actions: {
    addTodo(text) {
      this.todos.push({ text, completed: false });
    },
    removeTodo(index) {
      this.todos.splice(index, 1);
    },
  },
});

I find Vue easier to use for arrays, object processing, and that trend continues for stores. But, I can totally understand how majority of the people out there feel as strong about React.

Performance

Performance has not been a straight-forward and direct aspect of choosing a frontend framework. Most of the apps I build use database, data fetching from multiple sources, and components like maps/ charts - all of which shift performance considerations elsewhere.

I tend to rely on and follow the benchmarks published by talented people out there.

There are not going to be any clear answers here, but Vue and React perform on par with each other.

Developer Experience

In general I am strongly biased towards the Vue way of working.

I have tried to change my ways multiple times - for React, Blazor, Astro, HTMX and so forth - but keep picking up Nuxt/Vue for real projects. This may be more of a reflection of my own laziness than anything else.

I do like the fact that -

  1. Vue is easy to learn and use
  2. Nuxt makes it quite easy to build apps
  3. Has a decent ecosystem of libraries and tools

React and NextJS have always been the tools that I will certainly choose in the “next” project.

  • A larger community implies many more people answering questions that an amateur like me tends to have
  • Many starter templates - including a few that are targeted at SaaS apps
  • More AI generator friendly since they are used by more people and have many more repositories available publicly. However, React Server Components and the frequent NextJS changes somewhat blunt that advantage
  • v0.dev is a significant advantage too
  • Framework of frameworks like RedwoodJS, BlitzJS help stitch together many other tools and libraries to create a full-stack app
  • Styling libraries like Chakra UI, mui seem to be far more popular than their Vue counterparts

Conclusion

There are no significant advantages or limitations of using either of the options.

I see my own Vue + Nuxt bias, which has nothing to do with the tools themselves. While I continue to use Vue, I will have a keen eye on how React Server Components evolve and how Vue Vapor potentially changes the game.

If you are new to the ecosystem, build a few apps with either and see which option you like more. You may find yourself using both at one time or the other - so don’t get too bogged down by the choices.

Stay in touch!
Share on

Prashanth Krishnamurthy
WRITTEN BY
Prashanth Krishnamurthy
Technologist | Creator of Things