N
Nuxt3w ago
Y

How to type a custom Nuxt plugin instance in TypeScript?

I'm integrating Keycloak into my Nuxt 4 app with SSR disabled (SPA mode). I created a plugin to initialize Keycloak and provide the instance globally via nuxtApp.provide('keycloak', keycloak). I want to: Make $keycloak strongly typed when using const { $keycloak } = useNuxtApp() Use this $keycloak instance to fetch data from a resource server (adding the access token automatically and refreshing it before expiry) Ensure the plugin execution order so that $keycloak is available before other plugins (e.g., Axios) use it What's the best way to structure my plugin, type declarations, and token refresh logic so that everything works smoothly in Nuxt 4 SPA mode?
2 Replies
kapa.ai
kapa.ai3w ago
Hi @Y. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To achieve strong typing and proper plugin structure for your Keycloak integration in Nuxt 4 SPA mode, follow these steps based on the official documentation and community best practices: --- 1. Strongly Type $keycloak in useNuxtApp() Nuxt automatically types plugin-provided helpers for useNuxtApp(), but IDEs may not always pick this up. To ensure strong typing, augment the NuxtApp and Vue component types in a types or index.d.ts file:
// types/keycloak.d.ts
import type Keycloak from 'keycloak-js'

declare module '#app' {
interface NuxtApp {
$keycloak: Keycloak
}
}

declare module 'vue' {
interface ComponentCustomProperties {
$keycloak: Keycloak
}
}
// types/keycloak.d.ts
import type Keycloak from 'keycloak-js'

declare module '#app' {
interface NuxtApp {
$keycloak: Keycloak
}
}

declare module 'vue' {
interface ComponentCustomProperties {
$keycloak: Keycloak
}
}
This ensures that const { $keycloak } = useNuxtApp() is strongly typed in your components and composables. If you use JetBrains IDEs, you may need to augment @vue/runtime-core as a workaround for a known issue [Custom plugin in nuxt 3 has unknown type]. --- 2. Plugin Structure and Token Refresh Logic Create your plugin in plugins/keycloak.client.ts (since SSR is disabled):
import Keycloak from 'keycloak-js'
import type { KeycloakInstance } from 'keycloak-js'

export default defineNuxtPlugin((nuxtApp) => {
const runtimeConfig = useRuntimeConfig()
const keycloak = new Keycloak({
url: runtimeConfig.public.KEYCLOAK_URL!,
realm: runtimeConfig.public.KEYCLOAK_REALM!,
clientId: runtimeConfig.public.KEYCLOAK_CLIENT_ID!,
})

// Initialize Keycloak and handle token refresh
keycloak.init({ onLoad: 'check-sso' }).then((authenticated) => {
if (authenticated) {
// Set up token refresh
setInterval(() => {
keycloak.updateToken(60) // refresh if token will expire in 60s
}, 60000) // check every minute
}
})
// Provide the instance globally
return {
provide: {
keycloak
}
}
})
import Keycloak from 'keycloak-js'
import type { KeycloakInstance } from 'keycloak-js'

export default defineNuxtPlugin((nuxtApp) => {
const runtimeConfig = useRuntimeConfig()
const keycloak = new Keycloak({
url: runtimeConfig.public.KEYCLOAK_URL!,
realm: runtimeConfig.public.KEYCLOAK_REALM!,
clientId: runtimeConfig.public.KEYCLOAK_CLIENT_ID!,
})

// Initialize Keycloak and handle token refresh
keycloak.init({ onLoad: 'check-sso' }).then((authenticated) => {
if (authenticated) {
// Set up token refresh
setInterval(() => {
keycloak.updateToken(60) // refresh if token will expire in 60s
}, 60000) // check every minute
}
})
// Provide the instance globally
return {
provide: {
keycloak
}
}
})
This makes $keycloak available everywhere via useNuxtApp() [Providing Helpers]. --- 3. Using $keycloak in Other Plugins (e.g., Axios) To ensure $keycloak is available before other plugins (like Axios), control the plugin registration order by prefixing filenames alphabetically:
plugins/
| - 01.keycloak.client.ts
| - 02.axios.client.ts
plugins/
| - 01.keycloak.client.ts
| - 02.axios.client.ts
This guarantees that 02.axios.client.ts can safely access useNuxtApp().$keycloak [Registration Order]. --- 4. Injecting the Access Token in Axios Requests In your Axios plugin, you can access the Keycloak instance and inject the token:
export default defineNuxtPlugin((nuxtApp) => {
const axios = $fetch.create({
// ...other config
async onRequest({ options }) {
const keycloak = nuxtApp.$keycloak
if (keycloak?.token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${keycloak.token}`,
}
}
}
})

return {
provide: {
axios
}
}
})
export default defineNuxtPlugin((nuxtApp) => {
const axios = $fetch.create({
// ...other config
async onRequest({ options }) {
const keycloak = nuxtApp.$keycloak
if (keycloak?.token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${keycloak.token}`,
}
}
}
})

return {
provide: {
axios
}
}
})
This ensures all requests include the latest access token [Custom $fetch]. --- 5. Summary - Type augmentation ensures strong typing for $keycloak. - Plugin structure with provide and token refresh logic keeps the token up-to-date. - Alphabetical plugin filenames guarantee execution order. - Axios plugin reads the token from $keycloak for authenticated requests. --- If you follow these steps, your Keycloak integration will be robust, type-safe, and compatible with Nuxt 4 SPA mode. If you encounter IDE-specific typing issues, refer to the linked JetBrains issue for workarounds [Custom plugin in nuxt 3 has unknown type]. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@690790328172675082> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?