[{"data":1,"prerenderedAt":555},["ShallowReactive",2],{"\u002Fblog\u002Fnuxt-middleware-practical-guide-saas-ai-apps-data":3},{"post":4,"surround":544},{"id":5,"title":6,"alternates":7,"authors":8,"badge":14,"body":16,"date":502,"dateModified":503,"description":26,"extension":504,"head":503,"hero_image_url":25,"json_ld":505,"meta":527,"navigation":528,"ogImage":503,"outbound_links":529,"path":530,"primary_keyword":531,"related_articles":532,"robots":503,"schemaOrg":503,"search_intent":533,"seo":534,"sitemap":535,"stem":536,"supporting_keywords":537,"tags":542,"__hash__":543},"blog_en\u002Fblog\u002Fnuxt-middleware-practical-guide-saas-ai-apps.md","Nuxt Middleware: A Practical Guide for SaaS and AI Apps",[],[9],{"name":10,"to":11,"avatar":12},"Tom Han","https:\u002F\u002Fx.com\u002Ftomhan245",{"src":13},"https:\u002F\u002Fcdn.shipahe.ad\u002Ftomhan.webp",{"label":15},"How-to",{"type":17,"value":18,"toc":479},"minimark",[19,28,32,35,40,43,73,76,83,87,287,291,295,303,307,310,314,323,327,335,339,383,386,397,400,404,421,465],[20,21,22],"figure",{},[23,24],"img",{"src":25,"alt":26,"style":27},"https:\u002F\u002Fshipahe.ad\u002Fimages\u002Fblog\u002Fnuxt-middleware-practical-guide-saas-ai-apps\u002Fpost-692.webp","Use Nuxt 3 middleware to guard auth, subscriptions, locales, admin, and analytics. Concrete patterns, code, and pitfalls for SaaS and AI apps.","max-width:100%;border-radius:12px",[29,30,31],"p",{},"If your app has logins, paid features, multiple languages, and an admin area, routing rules pile up fast. Nuxt middleware keeps those rules consistent, testable, and easy to change without scattering checks across components.",[29,33,34],{},"Below is a practical stack we use on SaaS and AI tools. It favors small, named middlewares, clear redirects, and server-first checks so users never see protected pages flash.",[36,37,39],"h2",{"id":38},"what-middleware-does-in-nuxt-3","What middleware does in Nuxt 3",[29,41,42],{},"Nuxt 3 gives you two layers:",[44,45,46,63],"ul",{},[47,48,49,53,54,58,59,62],"li",{},[50,51,52],"strong",{},"Route middleware",". Runs before navigating to a page. Ideal for auth, plan gating, locale redirects, analytics. Files live in ",[55,56,57],"em",{},"\u002Fmiddleware",". Name them for selective use, or add ",[55,60,61],{},".global"," to run on every navigation.",[47,64,65,68,69,72],{},[50,66,67],{},"Server middleware",". Runs on the server for every request handled by Nitro. Use it for request logging, bot filtering, strict headers, and webhook signature checks. Files live in ",[55,70,71],{},"\u002Fserver\u002Fmiddleware",".",[29,74,75],{},"Route middleware runs on both server and client. You can make decisions on the first request on the server to avoid flicker, then reuse the same checks on the client for internal navigations.",[77,78,79],"pre",{},[80,81,82],"code",{},"\u002F\u002F \u002Fmiddleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware((to) => {\n  const { user } = useSession() \u002F\u002F your app's composable\u002Fstore\n  if (!user.value) {\n    return navigateTo(`\u002Flogin?next=${encodeURIComponent(to.fullPath)}`)\n  }\n})",[36,84,86],{"id":85},"build-a-reliable-middleware-stack","Build a reliable middleware stack",[88,89,90,119,138,157,176,222,242,257],"ol",{},[47,91,92,97,98,115,118],{},[93,94,96],"h3",{"id":95},"write-the-rules-before-code","Write the rules before code","List the routes and the guardrails they need. A typical SaaS set:",[44,99,100,103,106,109,112],{},[47,101,102],{},"Public routes anyone can view.",[47,104,105],{},"Protected routes for logged-in users.",[47,107,108],{},"Paid routes for active subscribers.",[47,110,111],{},"Admin-only routes for your team.",[47,113,114],{},"Language-aware routes that honor a user’s locale.",[116,117],"br",{},"Write the behavior and the failure path in plain language. Example: “If a non-subscriber visits \u002Fgenerate, redirect to \u002Fpricing and remember where they came from.” This becomes the acceptance test for your middleware.",[47,120,121,125,126,129,130,135,137],{},[93,122,124],{"id":123},"create-an-authentication-guard","Create an authentication guard","Add ",[55,127,128],{},"\u002Fmiddleware\u002Fauth.ts"," and redirect to login when there is no session. Always carry the intended destination.",[77,131,132],{},[80,133,134],{},"\u002F\u002F \u002Fmiddleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware((to) => {\n  const { user } = useSession()\n  if (!user.value) {\n    return navigateTo(`\u002Flogin?next=${encodeURIComponent(to.fullPath)}`)\n  }\n})",[116,136],{},"If you use a Nuxt SaaS starter, session state and redirects are already wired, so the guard is a thin wrapper around a single source of truth.",[47,139,140,144,145,148,149,154,156],{},[93,141,143],{"id":142},"gate-paid-features","Gate paid features","Check subscription status in ",[55,146,147],{},"\u002Fmiddleware\u002Fpaid.ts",". If missing or expired, send users to pricing or checkout and preserve the next URL.",[77,150,151],{},[80,152,153],{},"\u002F\u002F \u002Fmiddleware\u002Fpaid.ts\nexport default defineNuxtRouteMiddleware((to) => {\n  const { user } = useSession()\n  const { plan } = useBilling() \u002F\u002F central plan\u002Fsubscription state\n  if (!user.value) {\n    return navigateTo(`\u002Flogin?next=${encodeURIComponent(to.fullPath)}`)\n  }\n  if (!plan.value?.active) {\n    return navigateTo(`\u002Fpricing?next=${encodeURIComponent(to.fullPath)}`)\n  }\n})",[116,155],{},"Keep plan state cached in session or a small store that is filled on login or page load so the middleware does not trigger extra round trips.",[47,158,159,163,164,167,168,173,175],{},[93,160,162],{"id":161},"protect-admin-routes","Protect admin routes","Verify role in ",[55,165,166],{},"\u002Fmiddleware\u002Fadmin.ts",". Fail closed to a safe page.",[77,169,170],{},[80,171,172],{},"\u002F\u002F \u002Fmiddleware\u002Fadmin.ts\nexport default defineNuxtRouteMiddleware(() => {\n  const { user } = useSession()\n  if (!user.value || user.value.role !== 'admin') {\n    return navigateTo('\u002Fdashboard')\n  }\n})",[116,174],{},"Mirror the role model from your Admin Panel to avoid drift. Admin checks should be strict and boring.",[47,177,178,182,183,219,221],{},[93,179,181],{"id":180},"handle-language-with-a-global-locale-middleware","Handle language with a global locale middleware","Pick a default on first visit, then respect user choice. Prefix the filename to control order.",[77,184,185,214],{},[80,186,187,188],{},"\u002F\u002F \u002Fmiddleware\u002F10-locale.global.ts\nexport default defineNuxtRouteMiddleware((to) => {\n  const cookie = useCookie('locale')\n  const { setLocale } = useI18n()\n",[29,189,190,191,195,196,198,199,202,203,205,206,209,210,213],{},"\u002F\u002F First visit: guess from Accept-Language, set cookie, redirect to prefixed path\nif (!cookie.value) {\nconst header = (process.server ? useRequestHeaders(",[192,193,194],"span",{},"'accept-language'",")",[192,197,194],{}," : navigator.language) || ''\nconst guessed = header.split(',')",[192,200,201],{},"0","?.split('-')",[192,204,201],{}," || 'en'\ncookie.value = guessed\nif (!to.path.startsWith(",[80,207,208],{},"\u002F${guessed}",")) {\nreturn navigateTo(",[80,211,212],{},"\u002F${guessed}${to.fullPath}",", { redirectCode: 302 })\n}\n}",[29,215,216],{},[80,217,218],{},"\u002F\u002F Later visits: apply the chosen locale without forcing redirects\nsetLocale(cookie.value)\n})",[116,220],{},"Let the in-app language switch update the cookie or profile so the middleware follows the user’s decision.",[47,223,224,228,229,239,241],{},[93,225,227],{"id":226},"track-analytics-without-flicker","Track analytics without flicker","Record pageviews in a global middleware. Emit on the server when possible, then fall back to a client call.",[77,230,231,234],{},[80,232,233],{},"\u002F\u002F \u002Fmiddleware\u002F20-analytics.global.ts\nexport default defineNuxtRouteMiddleware((to, from) => {\nconst analytics = useAnalytics()\nconst payload = { path: to.fullPath, referrer: from?.fullPath || null }",[29,235,236],{},[80,237,238],{},"if (process.server) {\nanalytics.page(payload)\n} else {\nrequestIdleCallback(() => analytics.page(payload))\n}\n})",[116,240],{},"Keep event names consistent so you can answer, “Which protected routes cause the most logins?” or “Which plan gates are hit most often?”",[47,243,244,248,249,251,252],{},[93,245,247],{"id":246},"use-server-middleware-for-low-level-checks","Use server middleware for low-level checks","Put request-wide concerns in ",[55,250,71],{},". Keep handlers fast and stateless.",[77,253,254],{},[80,255,256],{},"\u002F\u002F \u002Fserver\u002Fmiddleware\u002Fwebhooks.ts\nexport default defineEventHandler(async (event) => {\nif (event.path.startsWith('\u002Fapi\u002Fwebhooks\u002Fstripe')) {\nconst sig = getHeader(event, 'stripe-signature')\nconst body = await readRawBody(event)\nif (!verifyStripeSignature(body, sig)) {\nthrow createError({ statusCode: 400, statusMessage: 'Invalid signature' })\n}\n}\n})",[47,258,259,263,264,267,268,277,279,280,283,284,72],{},[93,260,262],{"id":261},"attach-middleware-in-one-obvious-place","Attach middleware in one obvious place","Use named middleware in pages via ",[80,265,266],{},"definePageMeta",". Leave a short comment at the top describing the policy.",[77,269,270],{},[80,271,272,273,276],{},"\u002F\u002F pages\u002Fgenerate.vue\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({ middleware: ",[192,274,275],{},"'auth', 'paid'"," })\n\u002F\u002F Policy: logged-in, active subscription required\n\u003C\u002Fscript>",[116,278],{},"Document exceptions in the same way. Login, signup, pricing, and error pages should be exempt from ",[55,281,282],{},"auth"," and ",[55,285,286],{},"paid",[36,288,290],{"id":289},"patterns-that-hold-up-in-production","Patterns that hold up in production",[93,292,294],{"id":293},"guest-to-paid-upgrade","Guest-to-paid upgrade",[29,296,297,298,283,300,302],{},"A public landing page links to an AI feature page that requires both ",[55,299,282],{},[55,301,286],{},". If the visitor is logged out, send them to sign up. If logged in but not paid, send them to checkout. On success, return to the feature page with inputs intact so they can continue the workflow.",[93,304,306],{"id":305},"localized-onboarding","Localized onboarding",[29,308,309],{},"The first visit sets the initial locale from headers and redirects to a language-prefixed path. Thereafter, respect the user’s switch. This avoids loops and fights with the user’s choice.",[93,311,313],{"id":312},"admin-only-moderation","Admin-only moderation",[29,315,316,317,283,319,322],{},"Admin routes run the ",[55,318,282],{},[55,320,321],{},"admin"," middlewares. Fail closed to the dashboard. Keep the Admin Panel and middleware checking the same role source of truth.",[93,324,326],{"id":325},"ai-usage-gates","AI usage gates",[29,328,329,330,283,332,334],{},"Gate costly operations like generation, uploads, or long-running jobs. Apply ",[55,331,282],{},[55,333,286],{}," to chat, text, and image routes. That lets you measure demand and control spend from day one.",[36,336,338],{"id":337},"pitfalls-tests-and-tooling","Pitfalls, tests, and tooling",[44,340,341,352,358,364,377],{},[47,342,343,346,347,283,349,351],{},[50,344,345],{},"Infinite redirects",". Whitelist login, signup, pricing, and error pages. Add a quick check at the top of ",[55,348,282],{},[55,350,286],{}," to skip on those routes.",[47,353,354,357],{},[50,355,356],{},"Client-only checks cause flicker",". Ensure the critical checks run during the first server navigation so protected content never flashes.",[47,359,360,363],{},[50,361,362],{},"Slow async in middleware",". Do not fetch user or plan data on every navigation. Hydrate a small store on login or initial load and read from it.",[47,365,366,369,370,283,373,376],{},[50,367,368],{},"Order surprises",". Global middlewares run in filename order. Prefix them, for example ",[55,371,372],{},"10-locale.global.ts",[55,374,375],{},"20-analytics.global.ts",", so intent is obvious in reviews.",[47,378,379,382],{},[50,380,381],{},"Untested failure paths",". Write unit tests for each rule and a few end-to-end checks: expired plan, revoked admin role, missing locale cookie.",[29,384,385],{},"Middleware is small, but everything around it is not. A ready-to-buy Nuxt boilerplate removes a lot of setup. The shipahe.ad Nuxt starter includes protected pages, multiple authentication options, payment processing for one-time and subscriptions, multi-language support, transactional emails, a typed database with ORM, S3-compatible file storage, AI chat and generation with switchable models, cron jobs, a blog powered by Nuxt Content, analytics tracking, SEO tools, and a prebuilt landing page. It is built to work well with AI coding tools like Cursor and Claude.",[29,387,388,389,396],{},"As you refine rules, capture what feels rough for users. A public feedback workflow makes patterns obvious. See their ",[390,391,395],"a",{"href":392,"rel":393},"https:\u002F\u002Fwww.feedjolt.com\u002Fen\u002Fblog\u002Fproduct-feedback-management-for-startups-practical-guide",[394],"dofollow","practical guide to product feedback and running a feature voting board"," for a simple way to collect requests, auto-merge duplicates, and rank what to build next.",[29,398,399],{},"If you want a Nuxt SaaS starter that keeps middleware focused on policy while auth, payments, i18n, and analytics are already wired, a production-grade template will save weeks. Buy a boilerplate when you want to launch sooner and spend your time on product value, not plumbing.",[36,401,403],{"id":402},"key-takeaways","Key takeaways",[44,405,406,409,412,415,418],{},[47,407,408],{},"Write routing rules in plain language first, then encode them as small, named middleware.",[47,410,411],{},"Use global middleware for locale and analytics, named middleware for auth, paid, and admin.",[47,413,414],{},"Run checks on the server during the first load to avoid flicker and leaks.",[47,416,417],{},"Keep middleware fast. Cache what you can and test failure paths.",[47,419,420],{},"A solid Nuxt starter kit gives you the surrounding auth, payments, i18n, and tracking so middleware stays simple.",[422,423,426,430,434,437,441,444,448,451,455,458,462],"section",{"className":424},[425],"post-faq",[36,427,429],{"id":428},"faq","FAQ",[93,431,433],{"id":432},"what-is-the-difference-between-route-middleware-and-server-middleware-in-nuxt-3","What is the difference between route middleware and server middleware in Nuxt 3?",[29,435,436],{},"Route middleware runs before navigating to a page and is great for auth, plan checks, locale, and analytics. Server middleware runs on every server request and is better for low-level tasks like request logging or webhook signature checks.",[93,438,440],{"id":439},"where-should-i-put-nuxt-middleware-files","Where should I put Nuxt middleware files?",[29,442,443],{},"Put route middleware in the \u002Fmiddleware directory. Add .global to the filename for middleware that should run on every navigation. Put server middleware in \u002Fserver\u002Fmiddleware.",[93,445,447],{"id":446},"how-do-i-prevent-infinite-redirects-with-auth-middleware","How do I prevent infinite redirects with auth middleware?",[29,449,450],{},"Exclude login, signup, pricing, and error pages from auth and paid checks. Also remember to pass the original destination as a query so you can return the user after login or checkout.",[93,452,454],{"id":453},"can-i-run-async-code-inside-middleware","Can I run async code inside middleware?",[29,456,457],{},"Yes, but keep it minimal and cache results. For example, store plan status on login so your paid middleware does not fetch it repeatedly on every navigation.",[93,459,461],{"id":460},"how-do-i-test-nuxt-middleware","How do I test Nuxt middleware?",[29,463,464],{},"Write unit tests for each middleware function and add a few end-to-end tests for critical routes. Test both success and failure paths, including expired plans and missing roles.",[422,466,467,471],{},[36,468,470],{"id":469},"recommended-resources","Recommended resources",[44,472,473,474],{},"\n  ",[47,475,476],{},[390,477,395],{"href":478},"https:\u002F\u002Ffeedjolt.com",{"title":480,"searchDepth":481,"depth":481,"links":482},"",2,[483,484,485,492,493,494,501],{"id":38,"depth":481,"text":39},{"id":85,"depth":481,"text":86},{"id":289,"depth":481,"text":290,"children":486},[487,489,490,491],{"id":293,"depth":488,"text":294},3,{"id":305,"depth":488,"text":306},{"id":312,"depth":488,"text":313},{"id":325,"depth":488,"text":326},{"id":337,"depth":481,"text":338},{"id":402,"depth":481,"text":403},{"id":428,"depth":481,"text":429,"children":495},[496,497,498,499,500],{"id":432,"depth":488,"text":433},{"id":439,"depth":488,"text":440},{"id":446,"depth":488,"text":447},{"id":453,"depth":488,"text":454},{"id":460,"depth":488,"text":461},{"id":469,"depth":481,"text":470},"2026-09-05",null,"md",{"@context":506,"@graph":507},"https:\u002F\u002Fschema.org",[508,512],{"@type":509,"headline":6,"description":26,"image":25,"inLanguage":510,"datePublished":511},"BlogPosting","en","2026-09-05 03:02:35",{"@type":513,"mainEntity":514},"FAQPage",[515,519,521,523,525],{"@type":516,"name":433,"acceptedAnswer":517},"Question",{"@type":518,"text":436},"Answer",{"@type":516,"name":440,"acceptedAnswer":520},{"@type":518,"text":443},{"@type":516,"name":447,"acceptedAnswer":522},{"@type":518,"text":450},{"@type":516,"name":454,"acceptedAnswer":524},{"@type":518,"text":457},{"@type":516,"name":461,"acceptedAnswer":526},{"@type":518,"text":464},{},true,[],"\u002Fblog\u002Fnuxt-middleware-practical-guide-saas-ai-apps","nuxt",[],"Informational",{"title":6,"description":26},{"loc":530},"blog\u002Fnuxt-middleware-practical-guide-saas-ai-apps",[531,538,539,540,541],"vue","saas","middleware","starter kit",[531,538,539,540,541],"kbVCtzLKeYoTRZl2djbRCmEXjf38AB4TC0kwK7I-0N4",[545,550],{"title":546,"path":547,"stem":548,"description":549,"children":-1},"Nuxt email templates: 7 examples for SaaS notifications","\u002Fblog\u002Fnuxt-email-templates-7-examples-for-saas-notifications","blog\u002Fnuxt-email-templates-7-examples-for-saas-notifications","Seven Nuxt email templates with subject lines, fields, i18n, cron, and deliverability tips. Ship reliable welcomes, receipts, trials, and more from day one.",{"title":551,"path":552,"stem":553,"description":554,"children":-1},"Nuxt multi-tenant SaaS architecture patterns that scale","\u002Fblog\u002Fnuxt-multi-tenant-saas-architecture-patterns-that-scale","blog\u002Fnuxt-multi-tenant-saas-architecture-patterns-that-scale","A practical guide to nuxt multi-tenant SaaS design. Learn viable routing, shared-DB isolation, billing, and testing patterns that scale without surprises.",1788595299560]