DEV Community

Cover image for Introduction to Nuxt 3 modules
Jakub Andrzejewski
Jakub Andrzejewski

Posted on • Updated on

Introduction to Nuxt 3 modules

Nuxt 3 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. 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 module as it is Nuxt 3 compatible, very well developed and documented.

Nuxt Strapi Module directory Structure

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 about creating Nuxt modules or you can visit Nuxt official documentation.

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:

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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

import { withDocus } from 'docus'

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

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

import { withDocus } from 'docus'

export default withDocus({
  rootDir: __dirname,
})
Enter fullscreen mode Exit fullscreen mode

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:

import colors from 'windicss/colors'

export default {
  theme: {
    colors: {
      gray: colors.coolGray
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.'
---
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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:

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

    <h2>{{ user }}</h2>

  </div>
</template>

<script lang="ts" setup>
const user = useStrapiUser()
</script>
Enter fullscreen mode Exit fullscreen mode

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

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'))
    })
  }
})
Enter fullscreen mode Exit fullscreen mode

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:

export * from './types'

declare module '@nuxt/schema' {
  interface ConfigSchema {
    publicRuntimeConfig?: {
      strapi?: StrapiOptions
    }
  }
  interface NuxtConfig {
    strapi?: StrapiOptions
  }
  interface NuxtOptions {
    strapi?: StrapiOptions
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

  await fetchUser()
})
Enter fullscreen mode Exit fullscreen mode

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.
import type { Ref } from 'vue'
import type { StrapiUser } from '../../types'
import { useState } from '#app'

export const useStrapiUser = (): Ref<StrapiUser> => useState<StrapiUser>('strapi_user')
Enter fullscreen mode Exit fullscreen mode
  • This composable is used to get the strapi version from the runtime config.
import type { StrapiOptionsVersion } from '../../types'
import { useRuntimeConfig } from '#app'

export const useStrapiVersion = (): StrapiOptionsVersion => {
  const config = useRuntimeConfig()
  return config.strapi.version
}
Enter fullscreen mode Exit fullscreen mode
  • This composable is used to get the strapi token
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
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

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']
})
Enter fullscreen mode Exit fullscreen mode

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

Top comments (10)

Collapse
 
davestewart profile image
Dave Stewart

Hey! Thanks for the article but it's fairly outdated now.

The module has changed considerably (as has Nuxt 3!).

Any ambition to do an updated version?

Collapse
 
jacobandrewsky profile image
Jakub Andrzejewski

Hey Dave!

Yeah, I published this article more than a year ago when nuxt 3 was actually in very early stages.

I am planning to do a new article in the upcoming weeks about the current approach to building nuxt modules :)

Collapse
 
davestewart profile image
Dave Stewart

Hey! Just seen this. Did you manage to get any new writing done? 😊

Thread Thread
 
jacobandrewsky profile image
Jakub Andrzejewski

Actually planned for next monday ;)

Image description

Thread Thread
 
davestewart profile image
Dave Stewart

It's almost like we planned this 😉🙏

Thread Thread
 
jacobandrewsky profile image
Jakub Andrzejewski • Edited

Actually, I have rescheduled it to the next week because for this week I got a really big requirement to create an article about Nuxt + Cloudinary (due to recent Cloudinary community projects release (dev.to/jacobandrewsky/optimized-im...)

But I have the draft ready so it is just a matter of waiting till next week :)

Stay tuned!

Thread Thread
 
jacobandrewsky profile image
Jakub Andrzejewski
Collapse
 
jannikbuscha profile image
Jannik Buscha

Hey, very nice article, I am currently trying to use strapi in nuxt3 however $strapi is no longer injected globally for me. I only get Unresolved variable $strapi / Cannot read properties of undefined (reading '$strapi') error messages. Do you have any idea what the problem could be?

Collapse
 
jacobandrewsky profile image
Jakub Andrzejewski

Hi Jannik, I am glad you liked the article!
I think that the current approach for accessing integration variables (like we used this.$strapi in Nuxt 2) is now transitioned into using nuxt app composable or runtime config composable (useNuxtApp/useRuntimeConfig). Can you try to use any of these composables in your setup function? Something like this

<script setup>
const { $strapi } = useNuxtApp();
const { $strapi } = useRuntimeConfig();
</script>
Enter fullscreen mode Exit fullscreen mode

useRuntimeConfig should do the thing due to this line -> github.com/nuxt-community/strapi-m... but useNuxtApp can be useful for some older Nuxt modules that used to override the context. Let me know if that fixed the issue for you.

Collapse
 
jannikbuscha profile image
Jannik Buscha

Have yet found something hidden in the docu regarding nuxt3 and strapi 4. $strapi was replaced by the auto imported useStrapi4. But thanks for your answer that I can use very well for older nuxt libraries :)