
My First Experience with Nuxt.js
From React to Vue: Why I Made the Jump
I’ll be honest - I had never written a single line of Vue.js code before this project. My Vue knowledge came entirely from Reddit discussions and episodes of Syntax.fm, one of my favorite tech podcasts.
What caught my attention were the constant comparisons between React/Next.js and Vue’s supposedly simpler approach. When developers criticized React’s complexity, Vue was always mentioned as the elegant alternative. Curiosity got the better of me, so I decided to dive in headfirst and build my first Nuxt.js project: desk.chalkey.org.
What I Discovered About Nuxt.js
After building a complete customer support desk application, here are the features that impressed me most:
1. The Power of useFetch
Nuxt’s useFetch
composable is a game-changer for data fetching. It handles loading states, error handling, and caching automatically - something that typically requires multiple libraries and custom hooks in React.
2. Universal Rendering Done Right
Nuxt’s universal rendering approach fascinated me. The concept is brilliant: initial server-side rendering (SSR) for SEO and performance, then seamless client-side rendering (CSR) for subsequent interactions. This hybrid approach gives you the best of both worlds while reducing server load.
3. Pinia + useFetch: A Powerful Combination
I experimented with combining useFetch
and Pinia to create data-fetching stores, and the results were impressive. The pattern enables optimistic updates at the application level - something React’s useOptimistic
hook can only handle at the component level.
Here’s how I implemented this pattern for ticket category management:
import type { H3Error } from "h3";
import { toast } from "vue-sonner";
import type {
TicketCategory,
CreateTicketCategory,
} from "~/lib/db/queries/category";
export const useTicketCategoryStore = (companyId: number) =>
defineStore(`useTicketCategoryStore-${companyId}`, () => {
// Fetch categories with built-in caching and error handling
const {
data: categories,
pending,
error,
} = useFetch<TicketCategory[]>(
() => `/api/company/${companyId}/categories`,
{
lazy: true,
key: "api-tickets-category",
},
);
// Optimistic update implementation
const createTicketCategory = async (values: CreateTicketCategory) => {
const tempId = Date.now();
const optimisticCategory: TicketCategory = {
id: tempId,
...values,
description: values.description || null,
};
// Immediately update UI (optimistic update)
categories.value = [...(categories.value || []), optimisticCategory];
try {
// Show loading toast and handle success/error states
toast.promise(
$fetch<TicketCategory>(`/api/company/${companyId}/categories`, {
method: "POST",
body: values,
}),
{
loading: "Creating new ticket category...",
success: (data: TicketCategory) => {
// Replace optimistic data with real server response
categories.value = (categories.value || []).map((item) =>
item.id === tempId ? data : item,
);
return `${data.title} has been added`;
},
error: (error: H3Error) =>
`Failed to create ticket category. ${error.statusMessage}`,
},
);
} catch (err) {
// Rollback optimistic update on error
categories.value = (categories.value || []).filter(
(item) => item.id !== tempId
);
toast.error("Failed to create ticket category", {
description: err instanceof Error ? err.message : "Unknown Error",
});
}
};
return {
categories,
pending,
error,
createTicketCategory,
};
});
Key Benefits of This Pattern
Immediate UI Response: Users see changes instantly, creating a snappy, responsive experience.
Automatic Error Handling: Failed requests automatically rollback the optimistic changes.
Global State Management: Unlike React’s component-level useOptimistic
, this pattern works across the entire application.
Built-in Caching: Nuxt’s useFetch
handles caching automatically, reducing unnecessary API calls.
Final Thoughts
While I’m still not sure if combining Pinia with useFetch
is considered a “best practice,” it definitely works well in practice. The developer experience is smooth, and the resulting user experience is excellent.
Coming from React, Nuxt.js feels refreshingly straightforward while still being powerful enough for complex applications. The framework’s opinionated approach removes many decision-making burdens that React developers face daily.
If you’re a React developer curious about Vue, I highly recommend giving Nuxt.js a try. You might be surprised by how much you enjoy the change of pace.