Lawrence Eagles Senior full-stack developer, writer, and instructor.

Scaffolding an app with Vue 3, Nuxt, and TypeScript

6 min read 1693

Scaffolding an App with Vue 3, Nuxt, and TypeScript

Introduction

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).

Why Nuxt?

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:

Feature packed

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 — SPA, dynamic server-side rending — SSR, and static site generation — SSG.

Enhancements

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.

We made a custom demo for .
No really. Click here to check it out.

And while Vue already has a great transition API, Nuxt improves upon this by handling some complex cases like transitioning between pages.

Optimizations

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.

Nuxt and TypeScript

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.

Getting Started

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:

  • Support for the new Nuxt fetch in v2.12+
  • Provides easy access to router, app, store within composition API setup() hook
  • Interact directly with your vue-meta properties Composition API within setup() hook
  • Provides a drop-in replacement for ref with automatic SSR stringification and hydration (ssrRef)
  • Written in TypeScript

In the next section, we would build a remote job board using Nuxt, TypeScript, and the Nuxt composition API.

Building our app

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:

Welcome to Nuxt Page

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:

Job Search Board Demo

Conclusion

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.

Experience your Vue apps exactly how a user does

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. https://logrocket.com/signup/

LogRocket is like a DVR for web 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 - .

Lawrence Eagles Senior full-stack developer, writer, and instructor.

One Reply to “Scaffolding an app with Vue 3, Nuxt, and TypeScript”

Leave a Reply