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.
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
}
}
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'
}
})
For this configuration file you can only use the rootDir: __dirname
part like this:
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:
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.
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:
<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
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 defaulthttp://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
}
}
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()
})
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')
- 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
}
- 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
}
- 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']
})
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.
Top comments (10)
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?
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 :)
Hey! Just seen this. Did you manage to get any new writing done? 😊
Actually planned for next monday ;)
It's almost like we planned this 😉🙏
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!
dev.to/jacobandrewsky/introduction... :)
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?
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
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.
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 :)