Arch Banner

Nuxt DevTools: The Ultimate Toolbox

Nuxt DevTools: The Ultimate Toolbox
  • Slides

note: this was prepared for VueConf Toronto 2023.

Hi everyone! Today, we'll explore the power and versatility of Nuxt DevTools and how it can enhance your Nuxt Development Experience.

What is Nuxt DevTools?

Nuxt DevTools is a Nuxt Module that provides a rich set of debugging and development tools. It's designed to simplify and optimize the development process, making it easier to build strong and performant Nuxt applications. It is built by Anthony Fu, and it's open-sourced under Nuxt repositories.

The purpose of it is simple, to speed up the development process and make it easier to debug and optimize our Nuxt projects. It provides a rich set of tools that we will explore in a bit.

Instalation

Nuxt DevTools comes pre-enabled when you initialize a new Nuxt project, so you don't need to worry about that. However, if you have a project without DevTools, you can simply use the wizard 🧙

terminalterminal
npx nuxi devtools enable

Features of Nuxt DevTools

Let's dive into a project and see Nuxt DevTools in action!

the video will be uploaded shortly! mean while you can explore Nuxt DevTools yourself!

So, now that we know what Nuxt DevTools looks like, what's your favorite tab?

Custom Module: CRUD Generator

Okay, Time to get our hands dirty :) Run the below command to generate the module's project.

terminalterminal
npx nuxi init my-module -t module-devtools

Module Structure

so if you go to my-module directory, you can see that we have 3 main directories:

  • client: this directory is responsible for the module's UI.
  • playground: a plain nuxt project to test your module.
  • src: the module's code

RPC

Now that we've set up the structure for our module, it's time to dive into one of the most exciting parts...

What is RPC?

RPC stands for Remote Procedure Call, a powerful concept that enables communication between different parts of your application. With RPC, you can trigger functions or actions on the server-side (module's code - src) from the client-side (module's UI - client). This opens up a world of possibilities for creating dynamic and interactive modules.

Coding Time

First of all, we need to define some types for our RPC. let's create a file: ~/src/types.ts

types.tstypes.ts
import type { Nuxt } from 'nuxt/schema'
import type { WebSocketServer } from 'vite'
import type { ModuleOptions } from './module'

export interface ServerFunctions {
  getModulesOptions(): ModuleOptions
  generateApiCRUD(): Promise
}

export interface ClientFunctions {}

export interface DevtoolsServerContext {
  nuxt: Nuxt
  options: ModuleOptions
  wsServer: Promise<WebSocketServer>
}

Now it's time to initialize our module's RPC. Begin by creating a file under ~/src/rpc.ts in your module's directory. This file will contain the functions that you can call from your module's UI to perform various actions on the server-side.

rpc.tsrpc.ts
import type { DevtoolsServerContext, ServerFunctions } from './types'

export function setupRPC(ctx: DevtoolsServerContext): ServerFunctions {
  return {
    getModulesOptions() {
      return ctx.options
    },
    generateApiCRUD() {
      // Add logic to generate CRUD files in the project's `/server/api/` directory.
    },
  }
}

In this example, we've created an main function to setup the RPC in out module which has 2 simple functions that we can call:

  • the getModulesOptions will simply pass the options of our module.
  • the generateApiCRUD will create CRUD(create, read, update, delete) files in projects /server/api/ directory.

Once you've defined your RPC functions, you can call them from your module's UI, creating dynamic interactions that enhance the user experience; but before that we have to add setupRPC to devtools.ts

devtools.tsdevtools.ts
import { Nuxt } from 'nuxt/schema'
import { existsSync } from 'fs'
import { Resolver } from '@nuxt/kit'

// import required files
import { extendServerRpc, onDevToolsInitialized } from '@nuxt/devtools-kit'
import type { ClientFunctions, ServerFunctions } from './types'
import type { ModuleOptions } from './module'
import { useViteWebSocket } from './utils'
import { setupRPC } from './rpc'

const DEVTOOLS_UI_ROUTE = '/__my-module'
const DEVTOOLS_UI_LOCAL_PORT = 3300

// add RPC_NAMESPACE
const RPC_NAMESPACE = 'devtools:rpc:my-module'

export function setupDevToolsUI(nuxt: Nuxt, resolver: Resolver) {
  const clientPath = resolver.resolve('./client')
  const isProductionBuild =  existsSync(clientPath)

  // Serve production-built client (used when package is published)
  if (isProductionBuild) {
    nuxt.hook('vite:serverCreated', async (server) => {
      const sirv = await import('sirv').then((r) => r.default || r)
      server.middlewares.use(
        DEVTOOLS_UI_ROUTE,
        sirv(clientPath, { dev: true, single: true }),
      )
    })
  }
  // In local development, start a separate Nuxt Server and proxy to serve the client
  else {
    nuxt.hook('vite:extendConfig', (config) => {
      config.server = config.server || {}
      config.server.proxy = config.server.proxy || {}
      config.server.proxy[DEVTOOLS_UI_ROUTE] = {
        target: 'http://localhost:' + DEVTOOLS_UI_LOCAL_PORT + DEVTOOLS_UI_ROUTE,
        changeOrigin: true,
        followRedirects: true,
        rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, ''),
      }
    })
  }

  // @ts-expect-error
  nuxt.hook('devtools:customTabs', (tabs) => {
    tabs.push({
      // unique identifier
      name: 'my-module',
      // title to display in the tab
      title: 'My Module',
      // any icon from Iconify, or a URL to an image
      icon: 'carbon:apps',
      // iframe view
      view: {
        type: 'iframe',
        src: DEVTOOLS_UI_ROUTE,
      },
    })
  })

  // setup RPC
  const wsServer = useViteWebSocket(nuxt)
  onDevToolsInitialized(async () => {
    const rpcFunctions = setupRPC({ options, wsServer, nuxt })

    extendServerRpc<ClientFunctions, ServerFunctions>(RPC_NAMESPACE, rpcFunctions)
  })
}

now let's create the util function useViteWebSocket

utils.tsutils.ts
import type { WebSocketServer } from 'vite'
import type { Nuxt } from 'nuxt/schema'

export function useViteWebSocket(nuxt: Nuxt) {
  return new Promise<WebSocketServer>((_resolve) => {
    nuxt.hooks.hook('vite:serverCreated', (viteServer) => {
      _resolve(viteServer.ws)
    })
  })
}

now the module side is done. let's go to client side. we need a RPC composable so we can call the module's RPC's. create a file at ~/client/composables/rpc.ts

rpc.tsrpc.ts
import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
import type { BirpcReturn } from 'birpc'
import { ref } from 'vue'
import type { NuxtDevtoolsClient } from '@nuxt/devtools-kit/dist/types'
import type { ClientFunctions, ServerFunctions } from '../../src/types'

const RPC_NAMESPACE = 'devtools:rpc:my-module'

export const devtools = ref<NuxtDevtoolsClient>()
export const devtoolsRpc = ref<NuxtDevtoolsClient['rpc']>()
export const rpc = ref<BirpcReturn<ServerFunctions, ClientFunctions>>()

onDevtoolsClientConnected(async (client) => {
  devtoolsRpc.value = client.devtools.rpc
  devtools.value = client.devtools

  rpc.value = client.devtools.extendClientRpc<ServerFunctions, ClientFunctions>(RPC_NAMESPACE, {
    // define your client functionts here.
  })
})

make sure to use the same RPC_NAMESPACE

so now we can simply call rpc in our client side and call a server rpc (e.g. getModulesOptions): ~/client/pages/index.vue

index.vueindex.vue
<script setup lang="ts">
import { rpc } from '../composables/rpc'

async function generateCRUD() {
  await rpc.value?.generateApiCRUD()
}
</script>

<template>
  <div>
    <!-- Add your UI components here -->
  </div>
</template>

Finished Project

And voilà! we're done. tried to keep this as simple as possible, you can find the source code of the finished project with a better architecture at github.

also there is a starter-project for DevTools module with RPC structure here.

you can learn more about Nuxt DevTools in it documentation

Real World Examples

Here are some real-world examples that demonstrate the versatility and power of Nuxt DevTools:

Q&A

Feel free to ask me any question! you can DM me on or

Sponsor