Extend Nuxt functionality with custom modules, layers, and composables for reusable logic.
# Nuxt Modules & Layers Guide for Google Antigravity
Master Nuxt 3 modules and layers architecture in your Google Antigravity projects. This guide covers module development, layer composition, and reusable code patterns for scalable Vue.js applications.
## Creating a Nuxt Module
Build reusable functionality as modules:
```typescript
// modules/analytics/index.ts
import { defineNuxtModule, addPlugin, createResolver, addImports } from "@nuxt/kit";
export interface ModuleOptions {
trackingId: string;
debug?: boolean;
respectDoNotTrack?: boolean;
pageTracking?: boolean;
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: "nuxt-analytics",
configKey: "analytics",
compatibility: {
nuxt: "^3.0.0",
},
},
defaults: {
debug: false,
respectDoNotTrack: true,
pageTracking: true,
},
setup(options, nuxt) {
const resolver = createResolver(import.meta.url);
// Validate options
if (!options.trackingId) {
console.warn("[nuxt-analytics] Missing trackingId. Analytics disabled.");
return;
}
// Add runtime config
nuxt.options.runtimeConfig.public.analytics = {
trackingId: options.trackingId,
debug: options.debug,
respectDoNotTrack: options.respectDoNotTrack,
pageTracking: options.pageTracking,
};
// Add plugin
addPlugin({
src: resolver.resolve("./runtime/plugin"),
mode: "client",
});
// Add composables
addImports([
{
name: "useAnalytics",
as: "useAnalytics",
from: resolver.resolve("./runtime/composables/useAnalytics"),
},
{
name: "useTrackEvent",
as: "useTrackEvent",
from: resolver.resolve("./runtime/composables/useTrackEvent"),
},
]);
// Add server middleware for SSR tracking
if (options.pageTracking) {
nuxt.hook("nitro:config", (nitroConfig) => {
nitroConfig.handlers = nitroConfig.handlers || [];
nitroConfig.handlers.push({
route: "/api/_analytics",
handler: resolver.resolve("./runtime/server/api/analytics"),
});
});
}
},
});
```
```typescript
// modules/analytics/runtime/plugin.ts
import { defineNuxtPlugin, useRuntimeConfig, useRouter } from "#imports";
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig().public.analytics;
const router = useRouter();
// Check Do Not Track
if (config.respectDoNotTrack && navigator.doNotTrack === "1") {
console.log("[Analytics] Respecting Do Not Track preference");
return;
}
// Initialize analytics
const analytics = {
initialized: false,
init() {
if (this.initialized) return;
// Load analytics script
const script = document.createElement("script");
script.async = true;
script.src = `https://analytics.example.com/js?id=${config.trackingId}`;
document.head.appendChild(script);
this.initialized = true;
if (config.debug) {
console.log("[Analytics] Initialized with ID:", config.trackingId);
}
},
trackPage(path: string) {
if (config.debug) {
console.log("[Analytics] Page view:", path);
}
window.analytics?.page(path);
},
trackEvent(name: string, properties?: Record<string, unknown>) {
if (config.debug) {
console.log("[Analytics] Event:", name, properties);
}
window.analytics?.track(name, properties);
},
};
// Initialize on client
analytics.init();
// Track page views
if (config.pageTracking) {
router.afterEach((to) => {
analytics.trackPage(to.fullPath);
});
}
return {
provide: {
analytics,
},
};
});
```
```typescript
// modules/analytics/runtime/composables/useAnalytics.ts
import { useNuxtApp } from "#imports";
export function useAnalytics() {
const { $analytics } = useNuxtApp();
return {
trackPage: (path: string) => $analytics.trackPage(path),
trackEvent: (name: string, properties?: Record<string, unknown>) => {
$analytics.trackEvent(name, properties);
},
identify: (userId: string, traits?: Record<string, unknown>) => {
$analytics.identify?.(userId, traits);
},
};
}
export function useTrackEvent() {
const { trackEvent } = useAnalytics();
return trackEvent;
}
```
## Nuxt Layers Architecture
Create reusable base layers:
```typescript
// layers/base/nuxt.config.ts
export default defineNuxtConfig({
// Base layer configuration
components: [
{
path: "./components",
pathPrefix: false,
},
],
css: ["./assets/css/base.css"],
modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt", "@vueuse/nuxt"],
tailwindcss: {
cssPath: "./assets/css/tailwind.css",
configPath: "./tailwind.config.ts",
},
pinia: {
storesDirs: ["./stores/**"],
},
runtimeConfig: {
public: {
apiBase: "",
},
},
// Shared app configuration
app: {
head: {
charset: "utf-8",
viewport: "width=device-width, initial-scale=1",
htmlAttrs: {
lang: "en",
},
},
},
});
```
```typescript
// layers/base/composables/useFetch.ts
import { useFetch as useNuxtFetch, useRuntimeConfig } from "#imports";
import type { UseFetchOptions } from "nuxt/app";
export function useApiFetch<T>(
url: string | (() => string),
options: UseFetchOptions<T> = {}
) {
const config = useRuntimeConfig();
return useNuxtFetch(url, {
baseURL: config.public.apiBase,
headers: {
Accept: "application/json",
},
onRequest({ options }) {
// Add auth token if available
const token = useCookie("auth_token").value;
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`,
};
}
},
onResponseError({ response }) {
if (response.status === 401) {
navigateTo("/login");
}
},
...options,
});
}
```
```vue
<!-- layers/base/components/BaseButton.vue -->
<template>
<button
:class="[
baseClasses,
variantClasses[variant],
sizeClasses[size],
{ 'opacity-50 cursor-not-allowed': disabled || loading },
]"
:disabled="disabled || loading"
v-bind="$attrs"
>
<span v-if="loading" class="mr-2">
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
fill="none"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
</span>
<slot />
</button>
</template>
<script setup lang="ts">
interface Props {
variant?: "primary" | "secondary" | "outline" | "ghost";
size?: "sm" | "md" | "lg";
disabled?: boolean;
loading?: boolean;
}
withDefaults(defineProps<Props>(), {
variant: "primary",
size: "md",
disabled: false,
loading: false,
});
const baseClasses = "inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2";
const variantClasses = {
primary: "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500",
outline: "border-2 border-indigo-600 text-indigo-600 hover:bg-indigo-50 focus:ring-indigo-500",
ghost: "text-gray-600 hover:bg-gray-100 focus:ring-gray-500",
};
const sizeClasses = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
};
</script>
```
## Using Layers in Projects
Extend base layers in applications:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
"./layers/base",
"./layers/auth",
"./layers/admin",
],
// Project-specific overrides
modules: [
"./modules/analytics",
],
analytics: {
trackingId: process.env.ANALYTICS_ID,
},
runtimeConfig: {
public: {
apiBase: process.env.API_BASE_URL,
},
},
});
```
Google Antigravity generates modular Nuxt architectures with reusable layers, custom modules, and composables for maintainable Vue.js applications.This Nuxt prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their nuxt implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Nuxt projects, consider mentioning your framework version, coding style, and any specific libraries you're using.