Nuxt Context: where is it in Nuxt 3

Created
Nuxt 2 Context
Nuxt 2 Context

In early 2019, I deconstructed and organised Nuxt 2 context keys on an A4 page for Nuxt 2.0. This is one of my most liked Nuxt 2 diagrams to date ❤️.

context keys provide additional information that we may need while working with components, plugins, route middleware or even composable now that the Vue 3 is the new default version.

If you have been using Nuxt for a while now, you may be curious about what happened to the Nuxt 2 context object in Nuxt 3.

Nuxt 2 context is still available, ...but only as a part of Nuxt 2 Bridge composition API. However, it is not recommended to use Nuxt 2 context, because it will not exist in Nuxt 3. This means that we are not going to have a single context object in Nuxt 3. After doing some research, I have learned that we have a collection of composable and utility functions - available in Nuxt 3 by default - that can help us access similar information.

Below is the high-level list of information that we are used to accessing from Nuxt context.

Let's see where we can find them in Nuxt 3 along with quick code samples for reference.

Environment variables

In Nuxt 2, we add an env object in nuxt.config that normally includes all environment variables. We then access those variables in the Nuxt context through the env key.

// Nuxt 2
// nuxt.config.js
export default {
  env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:3000'
  }
}
// Nuxt 2
// pages/index.vue
<script>
function (context) {
    const baseUrl = context.env.baseUrl
}
</script>

From v2.13, Nuxt introduced runtimeConfig to define environment variables, and $config key to access those variables.

runtimeConfig

Starting from Nuxt 3, we can completely rely on runtimeConfig to define both public and private runtime environment variables.👇

// nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  runtimeConfig: {
    // Private config that is only available on the server
    UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
    UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,

    // Config within public will be also exposed to the client
    public: {
      baseUrl: process.env.BASE_URL || 'http://localhost:3000'
    }
  },
})

We can then access these environment variables directly by using useRuntimeConfig composable on both client-side and server-side.

// server/api/count.ts
import { auth } from '@upstash/redis';
const config = useRuntimeConfig()

auth(config.UPSTASH_REDIS_REST_URL, config.UPSTASH_REDIS_REST_TOKEN);
// pages/index.vue
<script setup>
  const config = useRuntimeConfig()
  
  // instead of process.env you will now access config.public.baseUrl
  console.log(config.public.baseUrl)
</script>

Error handling

In Nuxt 2, we have an error function that handles errors on both client-side and server-side.

// Nuxt 2
error({
    message: 'Page not found',
    statusCode: 404
})

In Nuxt 3, three key stages of error handling are divided into individual helper methods:

  • useError
  • throwError
  • clearError

useError

useError returns global Nuxt error details, such as statusCode and message, that is being handled.

// error.vue
<script setup>
    // customise 404 message from script section
    const error = useError();
    if (error.value.statusCode === 404) {
      error.value.message = 'Oops! Page not found 😔';
    }
</script>

As per Nuxt convention, we use error.vue to create a custom error page for our Nuxt application. Back in Nuxt 2, we put error.vue inside /layout directory. But now in Nuxt 3, we can put error.vue directly in the root of the project.

throwError

throwError triggers a full-screen error page and can be called on both client-side or on server-side directly using:

  • route middleware,
  • plugins or 
  • setup() functions.
// plugins/my-plugin.ts

export default defineNuxtPlugin((nuxtApp) => {

  const config = useRuntimeConfig()

  nuxtApp.hook("vue:error", () => {
    return throwError("error in vue:error");
  });

  nuxtApp.hook("app:error", () => {
    return throwError("error in app:error");
  });

  nuxtApp.vueApp.config.errorHandler = (error) => {
    return throwError("global error handler");
  };
});

clearError

As the name suggests, clearError clears the current Nuxt error that's being handled.

clearError also takes an optional parameter - redirect - to redirect users to a different page if required.

<template>
  <!-- Redirect to home page -->
  <button @click="handleError">Clear errors</button>
</template>

<script setup>
  // clear error and redirect to home page
  const handleError = () => clearError({ redirect: '/' });
</script>

Read more about Error Handling on Nuxt 3 Documentation.

State management

In Nuxt 2, we have to create /store directory and add index.js to activate Vuex store in Nuxt application like below 👇

// store/index.js
export const state = () => ({
  counter: 0
})

useState

With Nuxt 3, we have two options to manage state.

  1. useState - This composable is provided by Nuxt 3 itself for application-wide state management.
  2. Pinia - Pinia is the new Vuex for Vue 3 application. It’s compatible with composition API and a recommended way to manage state in large Vue/Nuxt applications.
// set color state
const useColor = () => useState<string>('color', () => 'pink')

// access color state
const color = useColor()

// update color state
const color = useColor()
color.value = 'purple'
// store/themestore.js
import { defineStore } from 'pinia'

export const useThemeStore = defineStore('theme', {
  state: () => {
    return {
      color: 'pink'
    };
  },
})

Response and request

In Nuxt 2, res and req objects are available through the context object only on server-side.

// Nuxt 2
function (context) {
    if (process.server) {
    const { req, res } = context
    }
}

In Nuxt 3, we have a couple of options to access the incoming request as below.

  1. useRequestEvent composable, or
  2. useNuxtApp.ssrContext composable

useRequestEvent

We can use useRequestEvent composable in:

  • Nuxt page components,
  • components and
  • plugins.
// Get underlying request event
const event = useRequestEvent()

// Get the URL
const url = event.req.url

Since the incoming request is only accessible on server-side, useRequestEvent will return undefined on client-side.

Alternatively, we can also use useNuxtApp composable to access res and req objects for the current request.

useNuxtApp

We can access the incoming request here through ssrContext which is a part of useNuxtApp composable.

const nuxtApp = useNuxtApp()
const { ssrContext } = nuxtApp

Inside ssrContext, we can access http req and res.

const host = ssrContext.event.req.headers.host

ssrContext is available to use on server-side, in:

  • page components
  • components
  • composable
  • server-side Nuxt plugins and
  • route middleware

📍 Always be sure to check the process.server condition before accessing the ssrContext.

// pages/index.vue | app.vue | components/component.vue
<script lang="ts">
export default defineComponent({
    async setup() {
        const NuxtApp = useNuxtApp();
        if (process.server) {
            const { ssrContext } = NuxtApp
        }
    }
});
</script>

Route details

In Nuxt 2, the route object is available through Nuxt context on both server-side and client-side.

useRoute

In Nuxt 3, we have useRoute composable that returns the current route including route parameters and query parameters, if applicable.

useRoute must be called in: 

  • setup function,
  • plugin, or
  • route middleware.

Dynamic page example

// pages/posts/[slug].vue
<script setup>
const route = useRoute()
const slug = route.params.slug

const { data: mountain } = 
    await useFetch(`https://api.nuxtjs.dev/mountains/${slug}`)
</script>

<template>
  <div>
    <h1>{{ mountain.title }}</h1>
    <p>{{ mountain.description }}</p>
  </div>
</template>
// pages/posts/_slug.vue
export default {
  async asyncData(context) {
    const { route, params, query } = context
    const slug = params.slug

    // Using the nuxtjs/http module here exposed via context.app
    const post = await context.app.$http.$get(
      `https://api.nuxtjs.dev/mountains/${slug}`
    )
  }
}

Read more about useRoute composable on Vue Router documentation.

Vue instance

In Nuxt 2 context, app is available as the global instance of the Vue application.

This root Vue instance includes all your client-side plugins. For example, when using i18n, you can get access to $i18n through context.app.i18n.

useNuxtApp

In Nuxt 3, the same global Vue instance can be accessed through useNuxtApp composable.

const { nuxtApp } = useNuxtApp()
// nuxtApp.vueApp
// pages/index.vue
export default {
  async asyncData(context) {
    // context.app
  }
}

Plugin example

We can use vueApp to create Nuxt plugin that adds Google Analytics - vue-gtag - in Nuxt 3 application.

// Nuxt 3
// plugins/vue-gtag.client.js
import VueGtag from 'vue-gtag-next'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueGtag, {
    property: {
      id: 'GA_MEASUREMENT_ID'
    }
  })
})

Here's the list of APIs available in nuxtApp.vueApp.

Programmatic navigation

We do have access to all Vue router methods, such as back, forward, go, push and replace through useRouter composable.

Yet Nuxt 2 context provides a handy function called redirect that is available on both client-side and server-side. The redirect function helps programmatically navigate users to another route.

In Nuxt 3, we have a new utility function, called navigateTo, that works the same as the redirect function - on both server-side and the client-side.

// Nuxt 3
navigateTo({
   path: '/search',
   query: {
     name: name.value,
     type: type.value
   }
})
// Nuxt 2
// Syntax: redirect([status,] path [, query]) 
redirect({ name: 'slug', params: { slug: mySlug } })

navigateTo takes two optional parameters, replace and redirectCode.

On the client-side, navigateTo function is a wrapper around two popular Vue router methods, router.replace and router.push.

Optional parameter replace is used on client-side to determine whether to replace the route or push the route.

await navigateTo('/', { replace: true })

On server-side, navigateTo uses sendRedirect function provided by h3 to handle redirection on the server-side.

sendRedirect(res, location, code=302)

Optional parameter redirectCode is used on the server-side, and it defaults to 302.

We can access navigateTo function in:

  • page components
  • components
  • composable
  • route middleware
  • Nuxt plugins

Route middleware example

The example below shows how we can redirect a user to the login page if the user is not authenticated.

// [Nuxt 3]
export default defineNuxtRouteMiddleware((to, from) => {
  const auth = useState('auth')
  if (!auth.value.authenticated) {
    return navigateTo('/login')
  }
})

Learn more about h3.

Passing server-side data onto client-side

In Nuxt 2, we have access to beforeNuxtRender function on server-side. Then, using nuxtState on the client-side, we can pass data from server-side to client-side as below.

// Nuxt 2
// plugins/my-plugin.js

export default ({ beforeNuxtRender, nuxtState }) => {
  if (process.server) {
    beforeNuxtRender(({ nuxtState }) => {
      nuxtState.color = 'pink'
    })
  } else {
    console.log('Custom data on the client side:', nuxtState.color)
  }
}

useState

useState section was updated on 22 May 2023.

In Nuxt 3, the same plugin can be rewritten using useState.

// Nuxt 3
// plugins/my-plugin.server.ts
export const useColor = () => useState<string>('color', () => 'pink')

export default defineNuxtPlugin((nuxtApp) => {
  const color = useColor()
})

📍 Since we are setting server-side data to be accessed on client-side, always be sure to check the process.server condition before setting the data.

📍 we can add .server postfix to make sure the Nuxt plugin runs only on the server-side.

As in for accessing this custom data on the client-side, we can use useState composable along with the key, i.e. color to access the data.

// useColor composable 
const color = useState('color')
// console.log(color.value) // 'pink'

Other build-time environment variables

In Nuxt 2 context, we have access to isDev, and even before that, in older versions of Nuxt, we had access to variables like, isServer and isClient.

These variables don't seem to be available in any of the existing composable of Nuxt 3 at the moment.

But if we think in terms of composable, then we can use useState to our advantage and expose required server-side information on the client-side.

In the example 👇, I have used useState to expose isDev and isClient variables on the client-side.

// plugins/variables.server.ts
export const useGlobalVariables = () => useState<object>('globalVariables', () => {
    return {
        isDev: process.env.NODE_ENV !== 'production',
        isClient: process.client,
    }
})

export default defineNuxtPlugin((nuxtApp) => {
  const globalVariables = useGlobalVariables()
})

Same as the previous example, for accessing these variables on the client-side, we can use useGlobalVariables key along with the useState composable.

// useColor composable 
const globalVar = useState('useGlobalVariables')
// console.log(globalVar.value)

When we console these values, they will reflect their respective environment. For example, isClient value will be false in terminal, and true in Browser console.

Conclusion

Here's the summary of all the key information that Nuxt 2 context provided with respective composable or utility functions can help us access the same information with a lot more flexibility than before.

· · ·

If you like to learn how to create educational concept diagrams like the one you see in this post, check out my articles on how to get started with your diagramming journey.

Follow me @krutiepatel to get notified of my upcoming content.

· · ·