How-to·

Nuxt Folder Structure: A Practical Step-by-Step Guide

A practical Nuxt folder structure guide with setup steps, examples, and pitfalls to avoid so your SaaS stays organized, predictable, and ready to ship.

Nuxt lets you move fast. Speed fades when folders drift, names get vague, and every new screen needs a scavenger hunt. A crisp Nuxt folder structure keeps work predictable, reviews short, and changes safe. The patterns below hold up for MVPs and full SaaS apps with auth, billing, admin, i18n, and AI features.

The aim is simple. Make it obvious where things live, keep layers thin, and let Nuxt conventions do the wiring so you write feature code, not glue.

How Nuxt maps folders to behavior

Nuxt favors convention over custom setup. Use these defaults and your app stays discoverable as it grows.

  • pages/. Files become routes. pages/index.vue is /. pages/pricing.vue is /pricing. Dynamic and nested routes use brackets and folders like pages/users/[id].vue and pages/users/[id]/settings.vue. Catch all with [...slug].vue when needed.
  • layouts/. Page wrappers. Add layouts/default.vue and any section-specific layout such as layouts/dashboard.vue. Opt in from a page with definePageMeta({ layout: 'dashboard' }).
  • components/. Auto imported by name. Favor domain folders like components/billing/CardForm.vue and components/admin/UserTable.vue. Use clear nouns so intent is obvious wherever the component is used.
  • composables/. Auto imported functions that hold state or logic. Use the useX naming pattern, for example composables/auth/useSession.ts or composables/billing/useInvoices.ts. Keep them pure and testable.
  • middleware/. Route middleware files, run before a page renders. Create middleware/auth.ts and call it by name in pages via definePageMeta({ middleware: 'auth' }). Add *.global.ts to run on all routes when you truly need it.
  • plugins/. Initialization that runs before app mount. Provide injections like $analytics or $payments. Scope to client or server with .client.ts or .server.ts, for example plugins/analytics.client.ts.
  • server/api/. Nitro server routes. File names map to endpoints and HTTP methods, such as server/api/invoices.get.ts, server/api/invoices.post.ts, or server/api/billing/webhook.post.ts. Keep business logic in server utils, not inlined in handlers.
  • assets/ vs public/. assets/ is built by Vite and supports imports in Vue and CSS. public/ is served as-is at the site root. Put imported images, fonts, and styles in assets/. Put files referenced by URL, like /robots.txt or /images/og-cover.jpg, in public/.
  • app.vue and app.config.ts. Your app shell and app-level meta. Centralize default SEO, headers, theme color, and root layout concerns here.
  • error.vue. Global error UI for unhandled exceptions and 404s. Keep it minimal and fast.

A clean, repeatable structure in 7 steps

1) Create the baseline directories

Start with pages/, layouts/, components/, composables/, plugins/, middleware/, server/api/, assets/, public/, app.vue, and app.config.ts. That backbone prevents ad hoc folders later.

2) Group by feature inside the standard folders

Favor domain folders rather than deep nested pages. Keep Nuxt auto imports working by nesting inside conventional folders.

  • Pages: pages/billing/index.vue, pages/billing/subscribe.vue, pages/account/index.vue
  • Components: components/billing/CardForm.vue, components/account/ProfileForm.vue
  • Composables: composables/billing/useInvoices.ts, composables/account/useProfile.ts
  • API: server/api/billing/checkout.post.ts, server/api/account/profile.get.ts

Rule of thumb: if a feature spans pages, UI, logic, and API, it earns a folder with the same name across those layers. Anyone can jump to the feature across the stack in seconds.

3) Use clear naming conventions

  • Components in PascalCase with a concrete noun, optionally prefixed by domain: BillingPlanPicker.vue, InvoiceTable.vue, CardForm.vue.
  • Composables prefixed with use and a verb or domain: useInvoices.ts, billing/useCheckout.ts, auth/useSession.ts.
  • API routes with method suffixes: invoices.get.ts, checkout.post.ts, users/[id].patch.ts.
  • Route files in kebab-case: user-settings.vue, team-members.vue. Keep route depth shallow and link deeper UI via components.

4) Keep auth and access control obvious

Put route guards in middleware/auth.ts and call them from protected pages using definePageMeta({ middleware: 'auth' }). For server routes, check the session at the top of each handler and bail fast on failure. Create pages/auth/ for login, register, and reset. Put all protected app screens under a clear path like pages/app/ or use a dedicated dashboard layout so access control stays visible in code review.

5) Isolate integrations in plugins

Initialize third-party clients in plugins/ and expose a single injection. Examples include analytics, a payment SDK, feature flags, and an i18n client. Use .client.ts for browser-only libraries and .server.ts for server-only setup. Keep business rules out of plugins and inside composables or server utils.

6) Separate static files from processed assets

Images imported in components belong in assets/ so Vite can optimize them. Files referenced by absolute path live in public/. This avoids surprising bundle size changes and broken URLs during deploys.

7) Keep environment and runtime config tidy

Put secrets in .env and map them into Nuxt runtime config. Expose only what the browser needs in runtimeConfig.public. Co-locate any shared types for config in types/. Do not import process.env across your app; read from runtime config in server and composables.

SaaS-ready examples by feature

Map common SaaS features to Nuxt folders so everything lines up the same way in every project.

  • Authentication
    • Pages: pages/auth/login.vue, pages/auth/register.vue, pages/auth/reset.vue
    • Composables: composables/auth/useSession.ts, composables/auth/useMagicLink.ts
    • API: server/api/auth/login.post.ts, server/api/auth/callback.get.ts
    • Middleware: middleware/auth.ts for protected pages
  • Billing
    • Pages: pages/billing/index.vue, pages/billing/subscribe.vue
    • Components: components/billing/PlanPicker.vue, components/billing/CardForm.vue
    • Composables: composables/billing/usePlans.ts, composables/billing/useCheckout.ts
    • API: server/api/billing/checkout.post.ts, server/api/billing/webhook.post.ts
  • Internationalization
    • Plugins: plugins/i18n.client.ts to register the i18n instance
    • Locales: locales/en.json, locales/fr.json with keys grouped by feature
    • Components: components/i18n/LanguageSwitcher.vue for in-app language changes
  • Admin
    • Pages: pages/admin/index.vue, pages/admin/users.vue
    • Layout: layouts/dashboard.vue for nav, sidebar, and breadcrumbs
    • API: server/api/admin/users.get.ts, server/api/admin/ban.post.ts
  • AI features
    • Pages: pages/ai/chat.vue, pages/ai/generate.vue
    • Composables: composables/ai/useChat.ts, composables/ai/useImageGen.ts
    • API: server/api/ai/chat.post.ts, server/api/ai/image.post.ts

If you are asking “How do I build and sell an AI tool online”, this mapping keeps auth, billing, and AI endpoints obvious from day one so you spend time on product behavior, not wiring.

Scaling without chaos

Practical guardrails

  • Prefer feature folders over deep nesting. Keep routes shallow. Push complexity into components and composables so URLs stay readable and reviews focus on one concern at a time.
  • Use layouts for app sections. A dashboard layout can own nav and chrome. Pages stay about content and data fetching.
  • Write a naming guide and enforce it. Add it to CONTRIBUTING.md. Lint and types in CI prevent broken imports and implicit any types from slipping in.
  • Document server endpoints. Keep a short server/README.md with routes, owners, and payload shapes. It pays for itself the first time you are on-call.

Common pitfalls to avoid

  • Mixing assets and public. Imported assets go in assets/. Files served by URL only go in public/. Mixing them causes bad caches and broken links.
  • Hiding logic in plugins. Plugins initialize libraries and provide injections. Put business logic in composables and server utils so it can be tested and reused.
  • Ambiguous component names. Table.vue is noise. InvoiceTable.vue or UserTable.vue explains intent instantly.
  • Dynamic routes without validation. Always validate params in server handlers. Use explicit types in composables that consume them.
  • No boundary between public and protected pages. Use a clear path like pages/app/ or a dedicated layout. Apply auth middleware every time.

When work goes beyond housekeeping, a partner like RedStudio can audit routes, components, and server handlers and suggest a pragmatic path to consolidate or split features.

When a starter kit saves weeks

If you would rather buy a Nuxt boilerplate and skip weeks of setup, a Nuxt SaaS starter kit provides a proven layout and production features. For example, the Nuxt SaaS boilerplate offered on shipahe.ad includes user authentication with email, password, magic links, and Google, protected pages, an admin panel, checkout flows for one-time or subscription payments with multiple providers, multi-language support with an in-app switch, transactional emails, a preconfigured database with an ORM, S3 compatible file storage, AI chat and generation tools, scheduled cron jobs, a blog, built-in analytics, and SEO tools. That frees your structure to focus on features instead of wiring signups, billing, and admin.

If you already have a codebase, you can still adopt a Nuxt starter template pattern over time. Move view files into pages/, extract logic into composables/, and fold server work into server/api/. Whether you call it a Nuxt starter kit, a Nuxt SaaS template, or a SaaS starter kit, the goal is the same. Make the tree predictable so you can add screens without rereading the whole app.

Key takeaways

  • Let Nuxt conventions guide your folders so discovery and auto imports work for you.
  • Group by feature inside standard directories. Keep routes shallow, move logic into composables.
  • Name files for what they do. Prefer explicit names over generic ones.
  • Use plugins for setup, middleware for access control, and server/api for backend work.
  • Reach for a Nuxt SaaS boilerplate when you want production features and a sane structure on day one.

FAQ

What belongs in assets versus public in a Nuxt app?

Put styles and images you import from components in assets. Put files you want at stable URLs, like favicons, robots.txt, or large downloads, in public.

Where do I put API routes in Nuxt 3?

Place them in server/api. Name files with HTTP method suffixes like users.get.ts or checkout.post.ts. Nuxt’s Nitro server will route requests automatically.

Should I use a src directory with Nuxt?

You can. Many teams keep the root simple and place app files at the project root. If you prefer src/, configure it and keep tests, scripts, and tooling outside it.

How can I organize large features without losing auto imports?

Nest by domain inside components, composables, pages, and server/api. Nuxt will still auto import components and composables from subfolders.

How do I migrate an existing Vue app into Nuxt without chaos?

Move routes into pages first to get routing under control, extract shared logic into composables, and then migrate API calls into server/api handlers.

Ready to ship your SaaS?

Everything you need is already built. Start today.
See Demo