Nuxt is a high-level framework on top of the Vue.js framework. Out the box, Nuxt comes with popular Vue libraries such as vue-router, vue-meta, and Vuex for global state management.
Also, Nuxt uses a couple of build tools under the hood such webpack, Babel, and PostCSS. And these are all preconfigured out of the box.
Being inspired by Next.js, Nuxt provides dynamic server-side rendering (SSR).
If Vue is already a framework, one might ask, why Nuxt?
While Vue is a great framework, Nuxt provides some extra exciting features such as dynamic server-side rendering out of the box. Also, Nuxt aims at making a developer’s life easier by providing optimized build tools configurations out of the box and by providing an abstraction layer onto of some fairly complex Vue concepts, e.g., routing.
We could sum up the need for Nuxt in three points:
Nuxt ships with Vue and many popular Vue libraries such as vue-router for routing, vuex for global state management, and vue-meta for meta tags handling.
Also, Nuxt is very versatile. We can use Nuxt for building single-page apps (SPAs), dynamic SSR, and static site generation (SSG).
Nuxt uses the file-system routing, which is an abstraction layer on top of the vue-router, to automate the generation of the vue-router configuration based on the file tree of the files in the page
directory.
Also, Nuxt supercharges Vue components with the concept of layout components and Nuxt components.
And while Vue already has a great transition API, Nuxt improves upon this by handling some complex cases like transitioning between pages.
Out of the box, Nuxt provides optimized configurations for build tools such as PostCSS, webpack, and Babel.
Also, with SSR support, good meta tags handling, and file-system routing for generating SEO-friendly URLs, our Nuxt application can be optimized for SEO.
Other optimizations we get out of the box are smart prefetching, automatic code splitting, and asset fingerprinting or cache bursting.
TypeScript, in a nutshell, is JavaScript plus a type system — to help us catch errors during development.
Besides the above, TypeScript also provides a form of self-documentation for our codes that is particularly useful in large applications.
Nuxt TypeScript support is provided mainly by two main Nuxt packages, namely @nuxt/typescript-build
— which provides support in layouts, components, plugins, and middlewares — and @nuxt/types
— which contains Nuxt TypeScript type definitions and is maintained alongside the Nuxt core.
We can install these packages by running:
yarn add --dev @nuxt/types # OR npm install --save-dev @nuxt/types
Besides these packages, Nuxt also provides an optional TypeScript runtime support using the @nuxt/typescript-runtime
module. According to the documentation, TypeScript runtime support is needed for files such as the nuxt.config
file, local modules, and serverMiddlewares
that are not compiled by webpack.
After installation, further configuration is required to provide TypeScript support for core Vue features such as the Options API, Class API, stores, and more. You can learn all about these in our previous article on Nuxt.js and TypeScript.
In the article, we are focused on working with TypeScript and the Vue 3 Composition API.
Let’s get started with this in the next section.
By default, the Vue Composition API does not cover Nuxt specific features like server-side rending. And the Nuxt Composition API was developed for this purpose; it combines the Vue composition API with all Nuxt features.
According to the documentation, The Nuxt composition API module automatically installs @vue/composition-api
as a plugin, so we do not need to enable it separately.
Also, for convenience, the Nuxt composition API module exports the @vue/composition-api
methods and hooks, so you can import directly from @nuxtjs/composition-api
.
@nuxtjs/composition-api
provides a way to use the Vue 3 Composition API with Nuxt-specific features. And according to the documentation, some of the features of the Nuxt composition API are:
setup()
hooksetup()
hookref
with automatic SSR
stringification and hydration (ssrRef
)In the next section, we would build a remote job board using Nuxt, TypeScript, and the Nuxt composition API.
First, we bootstrap our app by running:
yarn create nuxt-app <project-name> or npx create-nuxt-app <project-name>
It will ask you some questions on name, Nuxt options, UI framework, TypeScript, etc. Be sure to select TypeScript. This would install and configure the Nuxt TypeScript modules discussed above.
Now, install the Nuxt composition API package by running:
// change into project directory cd <project-name> // install package yarn add @nuxtjs/composition-api
Then enable the module by modifying the nuxt.config.js
file as seen below:
... buildModules: [ // https://go.nuxtjs.dev/typescript '@nuxt/typescript-build', // @nuxtjs/composition-api '@nuxtjs/composition-api/module' ], ...
Finally, start the app by running:
yarn start
We get:
Now let’s create our types
. Create a types directory containing a Jobs.ts
and an OrderItem.ts
file.
Add the following code to the Jobs.ts
file:
interface Job { title: string; location: string; salary: number; id: string; } export default Job;
and add the following code to the OrderItem.ts
file:
type OrderTerm = 'title' | 'salary' | 'location'; export default OrderTerm;
In the index.vue
file add the following code:
<template> <div class="app"> <header> <div class="title"> <h1>Remote Developer Jobs</h1> </div> <div class="order"> <button @click="handleClick('title')">Order by title</button> <button @click="handleClick('salary')">Order by salary</button> <button @click="handleClick('location')">Order by location</button> </div> </header> <JobList :jobs="jobs" :order="order" /> </div> </template> <script lang="ts"> import { defineComponent, ref } from '@nuxtjs/composition-api'; import Job from '@/types/Jobs' import OrderTerm from '@/types/OrderItems' import JobList from '@/components/JobList.vue' export default defineComponent({ name: 'App', components: { JobList }, setup() { const jobs = ref<Job[]>([ { title: 'React developer', location: 'New York', salary: 30000, id: '1' }, { title: 'Nodejs developer', location: 'London', salary: 40000, id: '2' }, { title: 'Flutter developer', location: 'Lagos', salary: 35000, id: '3' }, { title: 'Vuejs developer', location: 'Boston', salary: 21000, id: '4' }, { title: 'Svelte developer', location: 'San Francisco', salary: 32000, id: '5' } ]) const order = ref<OrderTerm>('title') const handleClick = (term: OrderTerm) => order.value = term return { jobs, order, handleClick } } }); </script> <style> header { text-align: center; } header .order { margin-top: 20px; } button { margin: 0 10px; color: #1195c9; border: 3px solid #1195c9; background: #d5f0ff; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: bold; } header .title{ display: flex; justify-content: center; } header img { width: 60px; margin-right: 20px; } header h1 { font-size: 3em; } </style>
In the code above, we defined our component by using the defineComponent
global method. This enables TypeScript to properly type-check the code inside our Vue component.
Also, we rendered our job list using the JobList
component, so let’s create that component. Delete the files in the components directory and create a JobList.vue
file inside. Add the following code to the JobList.vue
file:
<template> <div class="job-list"> <p>Ordered by {{ order }}</p> <transition-group name="list" tag="ul"> <li v-for="job in orderedJobs" :key="job.id"> <h2>{{ job.title }} in {{ job.location }}</h2> <div class="salary"> <p>{{ job.salary }} rupees</p> </div> <div class="description"> <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Rem omnis voluptatum eius doloremque optio iusto sequi dignissimos. Pariatur earum assumenda dolores possimus quidem quam, reprehenderit aliquid consequuntur amet non facere.</p> </div> </li> </transition-group> </div> </template> <script lang="ts"> import { defineComponent, PropType, computed } from '@nuxtjs/composition-api' import Job from '@/types/Jobs' import OrderTerm from '@/types/OrderItems' export default defineComponent({ props: { jobs: { type: Array as PropType<Job[]>, required: true }, order: { type: String as PropType<OrderTerm>, required: true } }, setup(props) { const orderedJobs = computed(() => { return [...props.jobs].sort((a: Job, b: Job) => { return a[props.order] > b[props.order] ? 1 : -1 }) }) return { orderedJobs } }, }) </script> <style scoped> .job-list { max-width: 960px; margin: 40px auto; } .job-list ul { padding: 0 } .job-list li { list-style-type: none; background: white; padding: 16px; margin: 16px 0; border: 2px solid #1195c9; border-radius: 4px; } .job-list h2 { margin: 0 0 10px; text-transform: capitalize; } .salary { display: flex; } .salary img { width: 30px; } .salary p { color: #17bf66; font-weight: bold; margin: 10px 4px; } .list-move { transition: all 1s; } </style>
Our JobList
component above takes two required
props, namely jobs
and order
. And we specified the types
of these props using type assertion. Type assertion is a TypeScript feature that enables us to specify types in cases where TypeScript is not able to infer the correct type., as seen in our component above, where the jobs
prop is an array of Jobs
while the order
prop is an array of OrderItem
.
So, by using propTypes
and generics, we specified the correct prop types using type assertion.
Finally, we have:
Nuxt is a great framework, out of the box it is feature-packed and it provides a lot of helpers to make a developer’s life easier.
TypeScript, on the other hand, helps developers catch errors as they develop and these redound to both the developer’s experience and the overall software quality.
The Composition API is a new API concept for writing Vue components. Although the Composition API does not deprecate the Options API, it is the preferred choice for building larger applications where sharing and reusing code is a major concern.
I hope that after going through this article, you have learned enough to start using these three tools in your project. And if you are interested in the source code, you can get the full code for our application on GitHub.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
2 Replies to "Scaffolding an app with Vue 3, Nuxt, and TypeScript"
I really love what you can do with Nuxt and Vue 3.
What? Nuxt is not yet compatible with Vue3!