Featured image of post Nuxt Learning Journey 1

Nuxt Learning Journey 1

This article is published on my personal website. Original link: Nuxt Learning Journey 1

Introduction

In the era of AI, being a simple “UI slicer” or having only basic knowledge of frontend engineering is no longer a high-value skill. To avoid being left behind by the rapidly advancing wheels of technology, I’ve decided to learn full-stack development while I’m still young. Since there are relatively few comprehensive Nuxt tutorials in Chinese, I’ve started this series to document my learning and help others avoid common pitfalls.

Installing Nuxt

I am using WebStorm to create the project directly. For beginners, convenience and avoiding initial setup errors should be the top priorities. Of course, creating a project with other IDEs is also simple. The following command creates a project using the latest version of Nuxt:

1
npm create nuxt@latest your-project-name

The subsequent process is the same regardless of the IDE. Nuxt will start an interactive configuration flow. Once completed, you can start the development server with npm run dev.

1
npm run dev

And just like that, our first Nuxt project is up and running!

Screenshot of a brand new Nuxt project

Convention Over Configuration

A key feature of Nuxt is Convention Over Configuration. In theory, we don’t need to write any configuration files; Nuxt works according to predefined conventions. For example, we don’t need to configure routes manually. By simply creating files in the pages directory, Nuxt automatically recognizes them and creates the corresponding routes.

Let’s try adding a login page:

Creating Pages

First, we create a pages directory inside the app directory. Create app/pages/index.vue and app/pages/login.vue, then add the relevant content. At this point, if we delete app.vue, Nuxt will automatically render index.vue as the homepage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({
  name: "index"
})
</script>

<template>
  <div>
    <NuxtLink to="/login">Go to Login Page</NuxtLink>
    <h2>This is the Homepage</h2>
  </div>
</template>

<style scoped>

</style>

Default Layout

So, what is app.vue? It is our default layout file, effectively the parent component for all pages. While deleting it lets Nuxt render index.vue by convention, let’s keep it and write some layout code:

1
2
3
4
5
6
<template>
  <div>
    <h1>This is the Default Layout!</h1>
    <NuxtPage />
  </div>
</template>

If Nuxt finds app.vue, it will use it to wrap and render index.vue as the homepage. Our page and project structure now look like this:

Project structure screenshot

Creating a Web API

Nuxt’s network interfaces are also convention-based and written in JavaScript, which is very friendly for frontend developers. Create a server directory in the project root (note: not inside the app directory), then create a server/api/hello.ts file. Nuxt will automatically create the /api/hello endpoint.

Let’s write a simple API to calculate the sum of two numbers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
export default defineEventHandler((event) => {
    // Get query parameters num1 and num2
    const query = getQuery(event)
    const num1 = Number(query.num1)
    const num2 = Number(query.num2)
    
    if(isNaN(num1) || isNaN(num2)) {
        // If parameters are not numbers, throw an error
        throw createError({
            status: 500,
            statusText: "Invalid query number",
        })
    } else {
        // Return the calculation result
        return num1 + num2
    }
})

Using the API in the Frontend

Let’s try calling this API in app/pages/index.vue. This code might look confusing at first glance, but I will explain it in parts below.

 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
38
39
40
41
42
43
44
45
46
47
<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({
  name: "index",
  data(): any {
    // This data, once processed by the server, will be sent to the frontend with the page
    return {
      result: 0,
      num1: 0,
      num2: 0,
    }
  },
  created(): any {
    // This part of the code executes on the server
    const route = useRoute()
    if(route.query.num1){ this.num1 = route.query.num1 }
    if(route.query.num2){ this.num2 = route.query.num2 }
    
    const data = useFetch('/api/hello',{
      query: { num1: this.num1, num2: this.num2 }
    })
    this.result = data.data
  },
  methods: {
    // This code executes in the browser
    async plus(){
      this.result = await $fetch('/api/hello',{
        query: { num1: this.num1, num2: this.num2 }
      })
    }
  }
})
</script>

<template>
  <div>
    <NuxtLink to="/login">Go to Login Page</NuxtLink>
    <h2>This is the Homepage</h2>
    <input v-model="num1"/> + <input v-model="num2"/> = {{result}}
    <button @click="plus">Calculate</button>
  </div>
</template>

<style scoped>

</style>

Let’s look at the server-side logic first:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // This part of the code executes on the server
    // useRoute() accesses route parameters
    const route = useRoute()
    if(route.query.num1){ this.num1 = route.query.num1 }
    if(route.query.num2){ this.num2 = route.query.num2 }
    
    // useFetch() is used to call APIs internally on the server
    const data = useFetch('/api/hello',{
      query: { num1: this.num1, num2: this.num2 }
    })
    
    // Assigning to data; Nuxt will render this into the page
    this.result = data.data

In Nuxt, the beforeCreate and created lifecycles are run on the server. We can pre-assemble the page on the server, much like PHP.

The page returned by the server is already rendered

As you can see, when we visit the page with parameters, the server returns the page with the result already calculated, unlike a traditional SPA that fetches an empty page and then fills in the data.

Now, let’s look at the browser-side code:

1
2
3
4
5
6
7
8
9
methods: {
    // This code executes in the browser
    async plus(){
      // $fetch() is the data request function provided by Nuxt
      this.result = await $fetch('/api/hello',{
        query: { num1: this.num1, num2: this.num2 }
      })
    }
  }

This is no different from an SPA. Binding this method to a button triggers a network request directly from the browser.

Browser initiates a network request

Summary

Today, we set up and experienced Nuxt for the first time. We created two pages and one API, using them on both the frontend and backend. We’ve caught a glimpse of Nuxt’s powerful server-side rendering capabilities and its seamless connection between the frontend and backend.

Licensed under CC BY-NC-SA 4.0
Last updated on Apr 06, 2026 00:00 UTC