auth for specific routes

I have pages like /account/ and /admin/ i want to protect so i made a middleware
'/account/**': {
meta: { requiredRole: 'user', middleware: ['auth'] },
},
'/admin/**': {
meta: { requiredRole: 'admin', middleware: ['auth'] },
},
'/account/**': {
meta: { requiredRole: 'user', middleware: ['auth'] },
},
'/admin/**': {
meta: { requiredRole: 'admin', middleware: ['auth'] },
},
I put this into my nuxt config routerules is this the right way to go about it compared to specifying the middleware per layout or per page?
14 Replies
kapa.ai
kapa.ai6d ago
Hi @WiFi Plug. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Currently, Nuxt does not natively support assigning route middleware via the routeRules property in your nuxt.config. The standard and documented way to protect specific routes is to specify middleware per page using definePageMeta in your page components, or to set middleware programmatically using hooks like pages:extend in your nuxt.config if you want to automate the process for multiple pages matching a pattern. For example, in a page component:
<script setup lang="ts">
definePageMeta({
middleware: ['auth']
})
</script>
<script setup lang="ts">
definePageMeta({
middleware: ['auth']
})
</script>
Or, to automate middleware assignment for multiple routes, you can use the pages:extend hook in your nuxt.config:
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
function setMiddleware (pages: NuxtPage[]) {
for (const page of pages) {
if (/* some condition for /account/** or /admin/** */) {
page.meta ||= {}
page.meta.middleware = ['auth']
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
}
}
})
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
function setMiddleware (pages: NuxtPage[]) {
for (const page of pages) {
if (/* some condition for /account/** or /admin/** */) {
page.meta ||= {}
page.meta.middleware = ['auth']
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
}
}
})
This approach allows you to programmatically assign middleware to groups of pages, but it is not the same as using routeRules. The routeRules property is used for configuring server-side behaviors (like caching, headers, or now, layouts with appLayout), but not for assigning client-side middleware as of the current Nuxt version. There is a recent PR to support layouts via routeRules (using appLayout), but not middleware assignment in this way [feat(nuxt): support appLayout in route rules]. In summary:
- Assigning middleware via routeRules is not currently supported for client-side route protection. - Use definePageMeta or the pages:extend hook to assign middleware per page or programmatically for groups of pages [middleware directory structure]. If you want to protect all pages under /account/ and /admin/, you should use one of these supported approaches. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai6d ago
<@679734274840330384> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
WiFi Plug
WiFi PlugOP6d ago
@kapa.ai okay so using the hooks then how would that looks in my case for all /account/ and /admin/ pages to set my auth middleware with the requiredrole set differently for each one
kapa.ai
kapa.ai6d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To programmatically assign the auth middleware and set a different requiredRole meta property for all /account/** and /admin/** pages using the pages:extend hook in your nuxt.config, you can use the following approach:
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
function setMiddleware(pages: NuxtPage[]) {
for (const page of pages) {
if (page.path?.startsWith('/account')) {
page.meta ||= {}
page.meta.middleware = ['auth']
page.meta.requiredRole = 'user'
}
if (page.path?.startsWith('/admin')) {
page.meta ||= {}
page.meta.middleware = ['auth']
page.meta.requiredRole = 'admin'
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
}
}
})
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
function setMiddleware(pages: NuxtPage[]) {
for (const page of pages) {
if (page.path?.startsWith('/account')) {
page.meta ||= {}
page.meta.middleware = ['auth']
page.meta.requiredRole = 'user'
}
if (page.path?.startsWith('/admin')) {
page.meta ||= {}
page.meta.middleware = ['auth']
page.meta.requiredRole = 'admin'
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
}
}
})
This code will: - Assign the auth middleware to all pages whose path starts with /account or /admin. - Set requiredRole to 'user' for /account/** pages and 'admin' for /admin/** pages. Note:
- This approach is documented as the way to set middleware and custom meta properties programmatically for groups of pages [middleware directory structure]. - The meta object can be used to pass custom properties like requiredRole to your middleware. You can then access to.meta.requiredRole inside your auth middleware to implement role-based protection logic. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai6d ago
<@679734274840330384> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
WiFi Plug
WiFi PlugOP6d ago
@kapa.ai so in my auth middleware I am using supabase for auth, and i have a profiles table with a user role associated with each user. I want the middleware to wait for my supabase auth status to be available and if not then it blocks page navigation to go to /login, if they are authenticated it then needs to wait for the profile to be grabbed with the role to check. if it's valid it allows loading the page otherwise they're kicked to /login. currently my middleware is loading the restricted page then kicking them off which isn't what i want. how can i achieve this blocking behaviour that waits for the auth status and profile role before navigating to the page otherwise kicking them out?
kapa.ai
kapa.ai6d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To achieve blocking navigation in Nuxt middleware—so that the restricted page does not render before authentication and role checks are complete—you should use an async route middleware and ensure you return a redirect (with navigateTo) before the page loads if the user is not authenticated or does not have the required role. Here’s how you can structure your middleware: 1. Use an async middleware: This allows you to await Supabase’s auth status and your profile fetch. 2. Check authentication first: If not authenticated, immediately redirect to /login. 3. Fetch the user profile and check the role: If the role is invalid, redirect to /login. 4. Do not return anything if the user is allowed: This lets the navigation proceed. Example:
export default defineNuxtRouteMiddleware(async (to) => {
// 1. Wait for Supabase auth status
const { auth } = useSupabaseClient()
const user = auth.user()

if (!user) {
// Not authenticated, redirect before page loads
return navigateTo('/login')
}

// 2. Fetch profile (assuming you have a composable or direct query)
const { data: profile, error } = await useAsyncData('profile', () =>
$fetch('/api/profile', { params: { userId: user.id } })
)

if (error.value || !profile.value) {
// Profile not found or error, redirect
return navigateTo('/login')
}

// 3. Check required role from route meta
const requiredRole = to.meta.requiredRole
if (requiredRole && profile.value.role !== requiredRole) {
// Role mismatch, redirect
return navigateTo('/login')
}

// 4. If all checks pass, do nothing (navigation continues)
})
export default defineNuxtRouteMiddleware(async (to) => {
// 1. Wait for Supabase auth status
const { auth } = useSupabaseClient()
const user = auth.user()

if (!user) {
// Not authenticated, redirect before page loads
return navigateTo('/login')
}

// 2. Fetch profile (assuming you have a composable or direct query)
const { data: profile, error } = await useAsyncData('profile', () =>
$fetch('/api/profile', { params: { userId: user.id } })
)

if (error.value || !profile.value) {
// Profile not found or error, redirect
return navigateTo('/login')
}

// 3. Check required role from route meta
const requiredRole = to.meta.requiredRole
if (requiredRole && profile.value.role !== requiredRole) {
// Role mismatch, redirect
return navigateTo('/login')
}

// 4. If all checks pass, do nothing (navigation continues)
})
Key points: - Using await in the middleware ensures navigation is blocked until checks are complete. - Returning navigateTo('/login') before the page loads prevents the restricted page from flashing. - You can access to.meta.requiredRole if you set it via the pages:extend hook as discussed earlier. This pattern is consistent with Nuxt’s recommended approach for protecting routes and blocking navigation until checks are complete [Protect App Routes]. If you’re still seeing the restricted page flash before redirect, double-check that your middleware is async and that you’re not using client-only checks or composables that are delayed until after the page renders. The middleware should handle all logic before the page is displayed. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai6d ago
<@679734274840330384> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
WiFi Plug
WiFi PlugOP6d ago
@kapa.ai oh well won't my supabase auth and profile fetching only run client side so that's why i see the page load? is there a workaround to ensure it's blocked client side too until it laods that stuff?
kapa.ai
kapa.ai6d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're correct that if your Supabase auth and profile fetching logic only runs on the client side, Nuxt will render the page first and then redirect after the checks, causing a "flash" of the restricted page. This happens because client-side middleware cannot block the initial render in the same way as server-side middleware. Workarounds and Nuxt Recommendations: - Server-side checks: If possible, perform authentication and role checks on the server during SSR. This way, navigation is blocked before the page is rendered. However, Supabase's client SDK typically only works in the browser, so this can be a limitation unless you use Supabase's server-side helpers or a custom API route to validate sessions server-side. - Blocking navigation with async middleware: Nuxt's async route middleware will block navigation until your checks are complete, but only if the data is available during SSR or hydration. If your auth/profile data is only available client-side, the initial SSR render can't block based on that data, and you'll see the page flash before redirect. - Client-only fetching: If you must fetch auth/profile data client-side, you can use a loading state or a splash screen to hide the page content until your checks are complete. This isn't a true "block," but it prevents users from seeing restricted content. - Example from Nuxt docs: The Nuxt docs recommend using middleware to protect routes, but note that client-side middleware is not automatically applied to all endpoints and will only run after hydration. This means unauthenticated users might briefly see the page before being redirected if checks are only client-side [Protect App Routes]. Summary:
If your authentication and profile checks are only available client-side, you cannot fully block the initial page render during SSR or hydration. The best you can do is use a loading state to hide content until checks are complete, or move as much logic as possible to the server side (e.g., via API routes or server middleware) to block navigation before rendering. This is a known limitation when using client-only authentication in Nuxt. If you need true blocking behavior, consider implementing server-side session validation for protected routes [Protect API Routes]. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai6d ago
<@679734274840330384> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
WiFi Plug
WiFi PlugOP6d ago
@kapa.ai thanks, i just had to remove a check which returned immediately if running server side and now it works properly
kapa.ai
kapa.ai6d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're welcome! Yes, removing the check that caused your middleware to return immediately on the server side is the correct approach. Nuxt's route middleware can run both on the server and client, and by allowing your authentication and profile checks to execute server-side, you ensure that navigation is properly blocked before the restricted page renders. This prevents the unwanted flash of protected content and provides a smoother user experience, as described in the Nuxt documentation and community examples [middleware directory structure]. If you need to access route meta (like requiredRole) or perform async checks, make sure your middleware is async and does not short-circuit on the server. This way, both SSR and client-side navigation are protected consistently. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai6d ago
<@679734274840330384> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?