Advanced

Real-life advanced usages of the strapi module.

Async data

To take full advantage of server-side rendering, you can use Nuxt useAsyncData composable:

<script setup lang="ts">
import type { Restaurant } from '~/types'

const route = useRoute()
const { findOne } = useStrapi()

const { data, pending, refresh, error } = await useAsyncData(
  'restaurant',
  () => findOne<Restaurant>('restaurants', route.params.id)
)
</script>

Server-Specific Configuration

You can apply configuration based on whether a request is processed on the browser or server by using Nuxt runtimeConfig. Options provided directly over runtimeConfig field will override options provided in the strapi field of Nuxt configuration.

export default defineNuxtConfig({
  // ...
  runtimeConfig: {
    strapi: { // nuxt/strapi options available server-side
      url: 'http://example-strapi-instance:1337'
    },
    public: {
      strapi: { // nuxt/strapi options available client-side
        url: 'https://strapi.example.com'
      }
    }
  },
  // nuxt/strapi options available on both client and server
  strapi: {
    prefix: '/api'
  }
  // ...
})

Auth middleware

You can protect your authenticated routes by creating a custom middleware in your project, here is an example:

middleware/auth.ts
export default defineNuxtRouteMiddleware((to, _from) => {
  const user = useStrapiUser()
  if (!user.value) {
    useCookie('redirect', { path: '/' }).value = to.fullPath
    return navigateTo('/login')
  }
})

Don't forget to reference your middleware in your page with:

pages/my-page.vue
definePageMeta({
  middleware: 'auth'
})

Errors handling

You can use the nuxt strapi:error hook to display a toast for example (the following example assumes that a $toast plugin has been injected).

Here are examples for both v4 and v3 as the signature between both versions is different.

Learn how to change the version in the options.

v4

plugins/strapi.client.ts
import type { Strapi4Error } from '@nuxtjs/strapi/dist/runtime/types/v4'

export default defineNuxtPlugin((nuxt) => {
  nuxt.hook('strapi:error' as any, (e: Strapi4Error) => {
    nuxt.$toast.error({ title: e.error.name, description: e.error.message })
  })
})

Check out the Strapi4Error type.

v3

plugins/strapi.client.ts
import type { Strapi3Error } from '@nuxtjs/strapi/dist/runtime/types/v3'

export default defineNuxtPlugin((nuxt) => {
  nuxt.hook('strapi:error' as any, (e: Strapi3Error) => {
    let description
    if (Array.isArray(e.message)) {
      description = e.message[0].messages[0].message
    } else if (typeof e.message === 'object' && e.message !== null) {
      description = e.message.message
    } else {
      description = e.message
    }

    nuxt.$toast.error({ title: e.error, description })
  })
})

Check out the Strapi3Error type.

Override Strapi /users/me route

Since v1.5.0 and Strapi v4.2.2, you can use the auth.populate option to populate data from /users/me route.

By default, when calling /users/me route, Strapi only returns the user populated with the role. Strapi User.me controller from the users-permissions plugin returns the ctx.state.user populated by the fetchAuthenticated method.

Here is how to override this method for both Strapi v3 and v4 by adding our own custom relation, in this example restaurants:

v4

src/index.js
module.exports = {
  register ({ strapi }) {
    strapi.service('plugin::users-permissions.user').fetchAuthenticatedUser = (id) => {
      return strapi
        .query('plugin::users-permissions.user')
        .findOne({ where: { id }, populate: ['role', 'restaurants'] })
    }
  }
}

Note that in Strapi v4, you must enable the restaurants.find permission in your admin for the Authenticated role to have the data populated.

v3

extensions/users-permissions/services/User.js
module.exports = {
  fetchAuthenticatedUser(id) {
    return strapi.query('user', 'users-permissions').findOne({ id }, ['role', 'restaurants'])
  }
}

File upload

On create and update routes, thanks to the Upload plugin Strapi lets you upload files related to an entry. To do so, you'll have to send a FormData.

Here is an example on how to upload an avatar file while creating a new entry in restaurants:

<script setup lang="ts">
import type { Restaurant } from '~/types'

const avatar = ref(null)
const form = reactive({ ... })

const client = useStrapiClient()

async function onSubmit () {
  try {
    const formData = new FormData()
    formData.append('files.avatar', avatar)
    formData.append('data', JSON.stringify(form))

    const { data } = await client<Restaurant>(`/restaurants`, {
      method: 'POST',
      body: formData
    })
  } catch (e) { }
}
</script>

Note that you have to use the client because create and update methods sends the body inside data.

Use Imported GraphQL

You can use an imported GraphQL query with the useStrapiGraphQL composable. To process imported GraphQL, you'll need to provide plugin for processing. An example setup with @rollup/plugin-graphql is shown below:

nuxt.config.ts
import gql from "@rollup/plugin-graphql"

export default defineNuxtConfig({
    // ...
    vite: {
        plugins: [ gql() ]
    }
})

You can now import a query like so:

Arguments on an imported GraphQL file must be defined on the query to be passed from the client.

<script setup lang="ts">
import query from "./query/example-query.gql"
const route = useRoute()
const graphql = useStrapiGraphQL()

const restaurant = await graphql(query, { id: route.params.id })
</script>

If importing a GraphQL query from TypeScript, you may encounter an error: "Cannot find module './query/example-query.gql' or its corresponding type declarations". You can resolve this error by creating a type declaration file within your project with the following contents:

globals.d.ts
declare module '*.gql' {
    import { DocumentNode } from 'graphql'
    const Schema: DocumentNode
    export = Schema
}