Introduction to Nuxt 3 modules
Tags: nuxt, module, nuxt3, vue
Published: 2022-01-03 09:42:30

[Nuxt 3](https://v3.nuxtjs.org/) beta dropped few months ago and with it, several modules have been updated to work well with a new Nuxt 3 architecture. For the previous version of Nuxt (also the most stable right now), we had a repository template for building new Nuxt 2 modules with it. These modules could be then released as NPM packages and easy downloaded for the users to provide useful functionality like PWA, i18n, Google Analytics and many more that you can check [here](https://modules.nuxtjs.org/). As there is no Nuxt 3 module template repository nor an article how to create one, I thought it would be a good idea to create one. 

In this article, we will be taking a look at [@nuxtjs/strapi](https://github.com/nuxt-community/strapi-module) module as it is Nuxt 3 compatible, very well developed and documented.

![Nuxt Strapi Module directory Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72x15bd35nrj83yd1j3w.png)

To make this article short and straight forward, I will focus only on the parts that will allow you to use the existing Strapi module and modify it to build your own module.

If you are completely new to creating Nuxt modules I would recommend reading first my [previous article](https://dev.to/baroshem/introduction-to-nuxt-modules-21a1) about creating Nuxt modules or you can visit Nuxt official [documentation](https://nuxtjs.org/docs/directory-structure/modules).

## `docs`

For Nuxt 3, recommended docs approach is to use a new tool called Docus. It allows to build markdown based applications very easily that are very, very fast as well (which is perfect for documentation websites).

### `docus.config.ts`

In this directory, you will have a `docus.config.ts` file that is responsible for your Docus configuration. The example from Strapi docs looks like follows:

```ts
export default {
  title: '@nuxtjs/strapi',
  url: 'https://strapi.nuxtjs.org',
  theme: {
    colors: {
      primary: '#8E75FF',
      prism: {
        background: '#F4F4F5 dark:#1F2937'
      }
    },
    header: {
      title: false,
      logo: {
        light: '/logo-light.svg',
        dark: '/logo-dark.svg'
      }
    }
  },
  twitter: '@nuxt_js',
  github: {
    repo: 'nuxt-community/strapi-module',
    branch: 'main',
    releases: true
  }
}
```

As you can see here, we are defining several properties here like SEO, colors, social media accounts, and more.  You can modify all the values here in order to suit your module (like your Twitter handle, colors, etc).

### `nuxt.config.js`

In this directory you will find a `nuxt.config.js` file as well but it will work a bit differently than what we usually had in Nuxt applications (you can ignore `buildModules` and `plausible` part as this is only related to Strapi module):

```ts
import { withDocus } from 'docus'

export default withDocus({
  rootDir: __dirname,
  buildModules: [
    'vue-plausible'
  ],
  plausible: {
    domain: 'strapi.nuxtjs.org'
  }
})
```

For this configuration file you can only use the `rootDir: __dirname` part like this:

```ts
import { withDocus } from 'docus'

export default withDocus({
  rootDir: __dirname,
})
```

### `windi.config.ts`

Docus uses WindiCSS by default as a styling and utility framework. In this file, you can set your WindiCSS configuration like this:

```ts
import colors from 'windicss/colors'

export default {
  theme: {
    colors: {
      gray: colors.coolGray
    }
  }
}
```

### `static`

In this directory, you can add custom icons, images, and logos for your module. The best approach here is to name the files the same way as the one provided by the Strapi module so that you would not have to modify other files to have the same result but with different images.

### `pages`

In this directory, you will define the pages of your documentation. `1.index` will be responsible for displaying homepage and you can add your custom SEO values like this:

```
---
title: Introduction
description: '@nuxtjs/strapi is a Nuxt 3 module for first class integration with Strapi.'
---
```

For other pages you can define them with a number, dot, and a name i.e. `3.Advanced`

## `example`

In this directory, you can test how your module works with the real Nuxt application without moving to another project. This directory also includes the `nuxt.config.ts` file and an example index page to display some result to the browser.

### `nuxt.config.ts`

Here, as with all Nuxt applications, you can define your Nuxt configuration with a new module (in this case, a Strapi module). We are importing our local module here and adding some configuration values like url to make it work as expected.

```ts
import { defineNuxtConfig } from 'nuxt3'
import module from '../src/module'

export default defineNuxtConfig({
  buildModules: [
    module
  ],
  strapi: {
    url: 'http://localhost:1337'
  }
})
```

Just keep in mind that there might be some issues with your module that you wont be able to discover with such a local testing. For that I would recommend using Verdaccio to imitate a real npm registry and try to use this package then.

### `pages/index.vue`

In this file, you can create your page with components in order to test how your module is behaving like this:

```vue
<template>
  <div>
    <h1>@nuxtjs/strapi</h1>

    <h2>{{ user }}</h2>
    
  </div>
</template>

<script lang="ts" setup>
const user = useStrapiUser()
</script>
```

## `src`

This directory is the most important part of your Nuxt 3 module. In here, you will be writing all your module logic, creating custom components or composables that will allow your users to use full functionality with the best Developer Experience possible.

### `module.ts`

In here you will define how your module will behave. This is rather a huge file but all things are important so bare with me. We will get through it together :D

```ts
import defu from 'defu'
import { resolve } from 'pathe'
import { defineNuxtModule, addPlugin } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: '@nuxtjs/strapi',
    configKey: 'strapi',
    compatibility: {
      nuxt: '^3.0.0',
      bridge: true
    }
  },
  defaults: {
    url: process.env.STRAPI_URL || 'http://localhost:1337',
    prefix: '/api',
    version: 'v4'
  },
  setup (options, nuxt) {

    // Default runtimeConfig
    nuxt.options.publicRuntimeConfig.strapi = defu(nuxt.options.publicRuntimeConfig.strapi, {
      url: options.url,
      prefix: options.prefix,
      version: options.version
    })

    // Transpile runtime
    const runtimeDir = resolve(__dirname, './runtime')
    nuxt.options.build.transpile.push(runtimeDir)

    // Add plugin to load user before bootstrap
    addPlugin(resolve(runtimeDir, 'strapi.plugin'))

    // Add strapi composables
    nuxt.hook('autoImports:dirs', (dirs) => {
      dirs.push(resolve(runtimeDir, 'composables'))
    })
  }
})
```

Nuxt Module configuration properties explained:

* `meta` - is responsible for providing meta information about your module like name, configKey, or Nuxt 3 compatibility.
* `defaults` - this object will be used when a user will not pass any data to your module. In the case of Strapi, if a user will not pass any custom Strapi url, then a default `http://localhost:1337` will be used instead. It works the same for any other configuration property defined in the defaults object.
* `setup` - this method is called when a module is being created. In here you can add properties defined in module configuration to public or private runtime config, register composables, add components, plugins, and many more.

If you want, you can also provide some type definitions here by including the following lines in your `module.ts` file:

```ts
export * from './types'

declare module '@nuxt/schema' {
  interface ConfigSchema {
    publicRuntimeConfig?: {
      strapi?: StrapiOptions
    }
  }
  interface NuxtConfig {
    strapi?: StrapiOptions
  }
  interface NuxtOptions {
    strapi?: StrapiOptions
  }
}
```

### `runtime/plugin.ts`

This file will be used to define a logic for an underlying Nuxt plugin that will be registered thanks to a module.

```ts
import { useStrapiAuth } from './composables/useStrapiAuth'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(async () => {
  const { fetchUser } = useStrapiAuth()

  await fetchUser()
})
```

In case of Strapi module, when registering the plugin, it will automatically try to fetch the user right after module initialization. In Nuxt 2, plugin was used mainly for extending the Nuxt context with a new variable like `$strapi` but in Nuxt 3 it can be also done thanks to public and private runtime config.

### `runtime/composables`

In here, you can define your custom composables that a user can try in their Nuxt 3 application. Composables can be used to provide many different functionalities. Let's take a look at following examples:

* This composable is used to register a state that is mainained both on the server and client by using `useState` composable.

```ts
import type { Ref } from 'vue'
import type { StrapiUser } from '../../types'
import { useState } from '#app'

export const useStrapiUser = (): Ref<StrapiUser> => useState<StrapiUser>('strapi_user')
```

* This composable is used to get the strapi version from the runtime config.

```ts
import type { StrapiOptionsVersion } from '../../types'
import { useRuntimeConfig } from '#app'

export const useStrapiVersion = (): StrapiOptionsVersion => {
  const config = useRuntimeConfig()
  return config.strapi.version
}
```

* This composable is used to get the strapi token

```ts
import { useCookie, useNuxtApp } from '#app'

export const useStrapiToken = () => {
  const nuxtApp = useNuxtApp()

  nuxtApp._cookies = nuxtApp._cookies || {}
  if (nuxtApp._cookies.strapi_jwt) {
    return nuxtApp._cookies.strapi_jwt
  }

  const cookie = useCookie<string | null>('strapi_jwt')
  nuxtApp._cookies.strapi_jwt = cookie
  return cookie
}
```

* And many more that you can check in the nuxt strapi module documentation or repository.

## `build.config.ts`

In here, you can define how your module should be built in order to have it ES modules form. You can define rollup settings, entries of the module files and external libraries that should not be compiled.

```ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  declaration: true,
  rollup: { cjsBridge: true },
  entries: [
    './src/module',
    { input: 'src/runtime/', outDir: 'dist/runtime' }
  ],
  externals: ['@nuxt/kit', '@nuxt/schema', 'vue']
})
```

## Summary

Now, you know how the Nuxt 3 compatible modules work and how to build one from scratch. Well done! This is however an introduction so if you want to dig deeper I would recommend reviewing the official docs, discord channel and github for more knowledge in this area.

## Bonus

* https://github.com/nuxt-community/strapi-module
* https://v3.nuxtjs.org/docs/directory-structure/plugins
* https://youtu.be/WjTIDGpq_JE