Nuxt Modules and Config
nuxt.config.ts basics, what a Nuxt module is, and building server API routes in server/api/.
nuxt.config.ts basics
nuxt.config.ts, at the project root, is where global configuration lives, wrapped in the defineNuxtConfig helper for type-checking and editor autocomplete:
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss'],
css: ['~/assets/main.css'],
runtimeConfig: {
apiSecret: '', // server-only, overridden by NUXT_API_SECRET env var
public: {
apiBase: '/api', // exposed to both server and client
},
},
app: {
head: {
title: 'My App',
},
},
});
runtimeConfig.public values are safe to reach from client-side code; anything else under runtimeConfig stays server-only, which is exactly where secrets like API keys belong.
What a Nuxt module is
A Nuxt module is a package that hooks into Nuxt's build and runtime lifecycle to add functionality with little or no manual setup — rather than wiring a library into your build config by hand, you add it to nuxt.config.ts and it configures itself. This is why the Nuxt ecosystem leans on modules so heavily: adding Tailwind, image optimization, or an ORM's dev tooling is typically a one-line addition instead of a webpack/Vite config change.
npm install @nuxtjs/tailwindcss @nuxt/image
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss', '@nuxt/image'],
});
Once listed in modules, each package's own setup logic runs during the build — the Tailwind module wires up its PostCSS pipeline; the image module registers the <NuxtImg>/<NuxtPicture> components and their optimization pipeline — without any further manual configuration for the common case.
Server API routes in server/api/
Alongside pages, Nuxt lets you write server-only endpoints directly in the same project, under server/api/:
// server/api/hello.ts
export default defineEventHandler((event) => {
return { message: 'Hello from the server' };
});
A file at server/api/hello.ts is reachable at /api/hello. Dynamic segments and reading the request body work much like a page's dynamic route:
// server/api/users/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id');
return { id, name: 'Ada Lovelace' };
});
// server/api/users.post.ts — the `.post` suffix restricts this file to POST requests
export default defineEventHandler(async (event) => {
const body = await readBody(event);
return { id: 101, name: body.name };
});
A page then calls it the same way it would call any other API, using useFetch:
<script setup lang="ts">
const { data } = await useFetch('/api/hello');
</script>
Code under server/ never ships to the browser, which makes it the correct place for database calls, private API keys, and anything else that must never be exposed client-side.
Common mistakes
- Installing a module's package but forgetting to add it to the
modulesarray innuxt.config.ts— the package alone does nothing until Nuxt is told to load it. - Putting a secret directly in
runtimeConfig.public(or in a composable that runs on the client) instead of the server-only part ofruntimeConfigorserver/api/— anything underpublicis shipped to and readable in the browser. - Naming two files such that their routes collide (e.g.
server/api/users.tsandserver/api/users/index.ts) — keep one consistent convention per resource.