Full Stack Development With Nuxt 3 and Nitro — Vue Amsterdam Conference 2022 — Ninth Talk

Full Stack Development With Nuxt 3 and Nitro — Vue Amsterdam Conference 2022 — Ninth Talk

Pooya Parsa - Head of Framework at Nuxt.js

Welcome! It's been a while since my last article, but I'm excited to return with the ninth part of my series on the Vuejs Amsterdam conference 2022. In this part, we will delve into the exciting world of full-stack development using Nuxt 3 and Nitro. You can watch the talk on JSWorld's YouTube channel as well.

Intro

I remember last time [two years ago] I was on the stage here, we had a really big dream of Nuxt to extending from an SSR application to something that we can use everywhere and beyond it.

To make this happen, they had to rewrite everything, which took more than 2 years. But before talking about Nuxt 3, Pooya talks about Nuxt 2, its strengths and limitations, and the reason behind starting with a project called Nitro which is a part of Nuxt 3.

Nuxt 2 Server Middleware

Nuxt 2 is a framework mainly focused on Server Side Rendering due to its benefits. Now that we already have a server, why not take this opportunity to extend it and add our APIs? Here's how we do it in Nuxt 2 using Server Middleware:

// server-middleware/test.ts
export default function (req, res) {
    res.end(JSON.stringify({ hello: 'world' }))
}

To register this middleware:

// nuxt.config
export default {
    serverMiddleware: {
        '/api/test': './server-middleware/test'
    }
}

Now if we call http://localhost:3000/api/test we get the response:

{
    "hello": "world"
}

As you can see it's plain simple node.js middleware, supports typescript and it had a basic router system using prefixes. But it has some limitations. For example, lack of dynamic path support in the route, no alias and transform support, and files are not bundled and are loaded in the runtime and we have to deploy the entire source code to the production to make it work.

Server Middleware HMR in Nuxt 2

Nuxt 2 is using an older version of express.js called connect which is lighter. On top of that, it has an isolation for SSR, so every code that you write like plugins and components is bundled through webpack or vite and is loaded in this isolated environment with HMR support.

It supports TypeScript with unjs/jiti and is fast at cache invalidation, but the server and node_modules were not isolated and the entire node_modules folder had to be deployed. Runtimes varied between production and development too.

Nuxt 2 with Express

We Could use nuxt 2 not only as a standalone framework but also as a middleware for an existing application. That gives us lots of flexibility but it comes with its own downsides.

  • It needs node_modules entirely to deploy to production, which limits us for some environments like serverless or makes it impossible in some other environments like edge rendering which are workers - Because we can not deploy node application with node_modules to edge.

  • It has a bad Developer Experience without server HMR and you have to reload the server every time you change something in the code.

  • It has unpredictable behaviors because it was designed to be used in specific ways.

API Calls in Nuxt 2

When you try to fetch something from the server side in your nuxt 2 application some strange things happen.

In this example, we are loading a product page in a webshop. In order to Server side render this endpoint we have to fetch the product's data, from the same API that is living on the same server. Maybe the only upside is that we have a shared server and there is no need to set up another server for the APIs.

But there are more downsides:

  • The base URL has to be manually configured and is a nightmare.

  • SSR API calls roundtrip in real-world apps to bring huge latency, making nuxt 2 apps slower than expected.

  • There is no built-in HTTP client.

Static Generation in nuxt 2

Nuxt 2 allows you to generate a static application that is not dependent on server files so you can deploy it everywhere.

When you run the nuxt generate command to generate the static app, nuxt will:

  • Build and bundle the whole app.

  • Load the bundle using a programmatic API to emulate the server.

  • Using this server and an internal API, nuxt tries to render each page and write them to the file system.

  • The dist/ is ready to use!

It is simple and fast in implementation, has full static support, and there is also a shared generation cache feature between pages. But there are also some issues with that.

  • You can either target server or static. There are many cases in that you can choose between one of them, but there are some use cases that need to have best of the both worlds.

  • Generation on demand is not possible and we have to pre-generate all of the pages every time on deployment.

  • The direct rendering method used in nuxt 2 makes also the internal code more complicated which was making some improvements harder.

Nuxt 2 server deployment

Deploying a nuxt 2 SSR App to production seems to be easy, but it's not efficient.

  • The whole repo must be deployed.

  • Only Node.js hosting is supported.

  • Extra unused node_modules.

Nitro

During an internal team meeting about cloud flare workers at the time that the team was actively working on nuxt 2, Pooya realized that nuxt was limited in some important aspects.

Nitro project started as an experiment on top of nuxt 2 thanks to the modularity and the hooks that it provides. They managed to create a proof of concept to deploy nuxt 2 apps to the edge and remove most of those limitations.

Nitro's goal according to Pooya is to deploy any JavaScript server anywhere you want, including edge, and he even claims that we can even run a package like express in the browser service worker using nitro.

Server Routes in Nuxt 3

To create a server route in Nuxt 3:

// server/api/hello.ts
export default () => ({ nuxt: "is easy!" })
// http://localhost:3000/api/hello
{
    "nuxt": "is easy!"
}

Also, it has a first-class integration with the runtime application and works out of the box:

// app.vue
<template>
    <div> {{ data }} </div>
<template>

<script setup>
    const { data } = await useFetch('/api/hello')
</script>

Some of the benefits are:

  • Minimalist API using unjs/h3 created by themselves, which is one of the fastest libraries.

  • Built using TypeScript

  • Fast router using unjs/radix3

  • Integrated HTTP client (fetch and $fetch)

  • It's platform agnostic, meaning you can write the code and expect it to run on every platform.

  • Lots of new built-in helpers

API Calls in Nuxt 3

In opposite to Nuxt 2 where we had to make a round-trip, here we have direct API calls with 0 latency, baseURL is automatically handled, and also fetch is improved with unjs/ohmyfetch.

Server HMR in Nuxt 3

In Nuxt 3, server Entry is also integrated into the same bundle compared to nuxt 2. Also, the entire server is isolated, and it's much faster in HMR. The new gotcha is that runtime isolation is more complex.

Pluggable KV Storage

Nitro comes with something called Pluggable Key-Value storage, which is powered by unjs/unstorage built by the nuxt team.

The unstorage solution is a unified and powerful Key-Value (KV) interface that allows combining drivers that are either built-in or can be implemented via a super simple interface and adding conventional features like mounting, watching, and working with metadata.

It works in all environments (Browser, NodeJS, and Workers) and offers multiple built-in drivers (Memory, FS, LocalStorage, HTTP, Redis).

Here you can see a snippet of an example of a simple note-taking app:

// server/api/notes/index.post.ts
export default defineEventHandler (async (event) => {
    const id = (await useStorage().getKeys( 'data: notes')) . Iength + 1
    const body = await useBody(event)
    const note = { ...body, id }
    await storage.setItem(`data:notes:${id}`, note)
    return { id }
])
// server/api/notes/[id].ts
export default defineEventHandler (async (event) =› {
    const { id ] = event. context. params
    const note = await useStorage().getItem(`data:notes:$(id)`)
    if (!note) {
        throw createError ({
            statusCode: 404,
            statusMessage: "Note not found!"
        })
    return note
})

Nuxt 3 server deployment

Deployment in Nuxt 3 is easier. You will fire nuxt build and the result will be a portable .outputdirectory with required public files and a tree-shaken version of the node_modules.

Static generation in Nuxt 3

Here are the steps from running the command to receiving the output:

In opposite to nuxt 2, here we have a hybrid solution to have the best of both worlds (server and static), and also generation on demand is now also possible.


End of the ninth Talk

I hope you enjoyed this part and that it was as valuable to you as it was to me. Over the next few days, I’ll share the rest of the talks with you. Stay tuned…