Skip to the content.

Nuxt.js Integration

This guide shows how to integrate Firebase ORM with Nuxt.js applications, covering both Nuxt 3 and Nuxt 2, with support for SSR, SSG, and SPA modes.

Quick Setup

1. Installation

npm install @arbel/firebase-orm firebase moment --save

For Nuxt 3, you might also want to install Nuxt Firebase module:

npm install @nuxtjs/firebase --save-dev

2. TypeScript Configuration

Update your tsconfig.json:

{
  "extends": "./.nuxt/tsconfig.json",
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false
  }
}

3. Nuxt Configuration

For Nuxt 3 (nuxt.config.ts):

export default defineNuxtConfig({
  // Enable SSR (default)
  ssr: true,
  
  // TypeScript configuration
  typescript: {
    typeCheck: true
  },

  // CSS framework (optional)
  css: ['~/assets/css/main.css'],

  // Runtime config for environment variables
  runtimeConfig: {
    // Private keys (only available on server-side)
    firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
    firebaseClientEmail: process.env.FIREBASE_CLIENT_EMAIL,
    firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY,
    
    // Public keys (exposed to client-side)
    public: {
      firebaseApiKey: process.env.NUXT_PUBLIC_FIREBASE_API_KEY,
      firebaseAuthDomain: process.env.NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
      firebaseProjectId: process.env.NUXT_PUBLIC_FIREBASE_PROJECT_ID,
      firebaseStorageBucket: process.env.NUXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
      firebaseMessagingSenderId: process.env.NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
      firebaseAppId: process.env.NUXT_PUBLIC_FIREBASE_APP_ID,
    }
  },

  // Build configuration
  build: {
    transpile: ['@arbel/firebase-orm']
  },

  // Vite configuration for client-side optimization
  vite: {
    define: {
      global: 'globalThis'
    },
    optimizeDeps: {
      include: ['firebase/firestore', 'firebase/app']
    }
  }
});

For Nuxt 2 (nuxt.config.js):

export default {
  // Nuxt mode
  mode: 'universal', // or 'spa'
  target: 'server', // or 'static'

  // Build configuration
  build: {
    transpile: ['@arbel/firebase-orm'],
    extend(config, { isClient }) {
      if (!isClient) {
        config.externals = config.externals || [];
        config.externals.push('firebase-admin');
      }
    }
  },

  // Environment variables
  publicRuntimeConfig: {
    firebaseConfig: {
      apiKey: process.env.NUXT_PUBLIC_FIREBASE_API_KEY,
      authDomain: process.env.NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
      projectId: process.env.NUXT_PUBLIC_FIREBASE_PROJECT_ID,
      storageBucket: process.env.NUXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
      messagingSenderId: process.env.NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
      appId: process.env.NUXT_PUBLIC_FIREBASE_APP_ID,
    }
  },

  privateRuntimeConfig: {
    firebaseAdminConfig: {
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    }
  }
};

Environment Configuration

Create a .env file:

# Public (client-side) variables
NUXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NUXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NUXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NUXT_PUBLIC_FIREBASE_APP_ID=your-app-id

# Private (server-side) variables
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

Firebase Setup

Nuxt 3 Plugin

Create plugins for Firebase initialization:

// plugins/firebase.client.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { FirestoreOrmRepository } from '@arbel/firebase-orm';

export default defineNuxtPlugin(async () => {
  const config = useRuntimeConfig();

  const firebaseConfig = {
    apiKey: config.public.firebaseApiKey,
    authDomain: config.public.firebaseAuthDomain,
    projectId: config.public.firebaseProjectId,
    storageBucket: config.public.firebaseStorageBucket,
    messagingSenderId: config.public.firebaseMessagingSenderId,
    appId: config.public.firebaseAppId,
  };

  // Initialize Firebase app
  const app = initializeApp(firebaseConfig);
  const firestore = getFirestore(app);

  // Initialize Firebase ORM for client-side
  FirestoreOrmRepository.initGlobalConnection(firestore);

  return {
    provide: {
      firestore
    }
  };
});
// plugins/firebase.server.ts
import admin from 'firebase-admin';
import { FirestoreOrmRepository } from '@arbel/firebase-orm';

export default defineNuxtPlugin(async () => {
  const config = useRuntimeConfig();

  // Initialize Firebase Admin SDK
  if (!admin.apps.length) {
    admin.initializeApp({
      credential: admin.credential.cert({
        projectId: config.firebaseProjectId,
        clientEmail: config.firebaseClientEmail,
        privateKey: config.firebasePrivateKey,
      }),
    });
  }

  const adminFirestore = admin.firestore();

  // Initialize Firebase ORM for server-side
  FirestoreOrmRepository.initGlobalConnection(adminFirestore, 'admin');

  return {
    provide: {
      adminFirestore
    }
  };
});

Nuxt 2 Plugin

// plugins/firebase.js
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { FirestoreOrmRepository } from '@arbel/firebase-orm';

export default ({ $config }, inject) => {
  // Client-side initialization
  if (process.client) {
    const firebaseConfig = $config.firebaseConfig;
    const app = initializeApp(firebaseConfig);
    const firestore = getFirestore(app);
    
    FirestoreOrmRepository.initGlobalConnection(firestore);
    
    inject('firestore', firestore);
  }
  
  // Server-side initialization
  if (process.server) {
    const admin = require('firebase-admin');
    
    if (!admin.apps.length) {
      admin.initializeApp({
        credential: admin.credential.cert($config.firebaseAdminConfig),
      });
    }
    
    const adminFirestore = admin.firestore();
    FirestoreOrmRepository.initGlobalConnection(adminFirestore, 'admin');
    
    inject('adminFirestore', adminFirestore);
  }
};

Model Definition

// models/user.model.ts
import { BaseModel, Model, Field, HasMany } from '@arbel/firebase-orm';
import { Post } from './post.model';

@Model({
  reference_path: 'users',
  path_id: 'user_id'
})
export class User extends BaseModel {
  @Field({ is_required: true })
  public name!: string;

  @Field({ is_required: true })
  public email!: string;

  @Field({ field_name: 'created_at' })
  public createdAt?: string;

  @Field({ is_required: false })
  public bio?: string;

  @HasMany({ model: Post, foreignKey: 'author_id' })
  public posts?: Post[];

  // Nuxt-specific helper methods
  toJSON() {
    return {
      id: this.getId(),
      name: this.name,
      email: this.email,
      createdAt: this.createdAt,
      bio: this.bio,
    };
  }

  static async findByEmail(email: string): Promise<User | null> {
    const users = await User.query().where('email', '==', email).get();
    return users.length > 0 ? users[0] : null;
  }

  getDisplayName(): string {
    return this.name || this.email;
  }
}

Nuxt 3 Pages and Components

Server-Side Data Fetching

<!-- pages/users/index.vue -->
<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold mb-6">Users</h1>
    
    <UserForm @user-created="handleUserCreated" />
    
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
      <UserCard
        v-for="user in users"
        :key="user.id"
        :user="user"
        @user-deleted="handleUserDeleted"
      />
    </div>
  </div>
</template>

<script setup>
import { User } from '~/models/user.model';

// Server-side data fetching
const { data: initialUsers } = await useLazyAsyncData('users', async () => {
  // This runs on server-side, using admin connection
  const users = await User.getAll();
  return users.map(user => user.toJSON());
});

// Reactive users list
const users = ref(initialUsers.value || []);

// Handle user creation
const handleUserCreated = (newUser) => {
  users.value.push(newUser);
};

// Handle user deletion
const handleUserDeleted = (userId) => {
  users.value = users.value.filter(user => user.id !== userId);
};

// Real-time subscription on client-side
onMounted(() => {
  if (process.client) {
    const unsubscribe = User.onList((user) => {
      const index = users.value.findIndex(u => u.id === user.getId());
      
      if (index >= 0) {
        users.value[index] = user.toJSON();
      } else {
        users.value.push(user.toJSON());
      }
    });

    onUnmounted(() => {
      if (unsubscribe) {
        unsubscribe();
      }
    });
  }
});

// SEO meta tags
useSeoMeta({
  title: 'Users - Firebase ORM Demo',
  description: 'List of all users in the system',
});
</script>

Dynamic Pages

<!-- pages/users/[id].vue -->
<template>
  <div class="container mx-auto px-4 py-8">
    <div v-if="pending" class="text-center">
      <div class="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900 mx-auto"></div>
      <p class="mt-4">Loading user...</p>
    </div>
    
    <div v-else-if="error" class="text-red-600 text-center">
      <p>Error loading user: </p>
    </div>
    
    <div v-else-if="user" class="max-w-2xl mx-auto">
      <div class="bg-white shadow-lg rounded-lg p-6">
        <h1 class="text-2xl font-bold mb-4"></h1>
        <div class="space-y-2">
          <p><strong>Email:</strong> </p>
          <p v-if="user.bio"><strong>Bio:</strong> </p>
          <p><strong>Created:</strong> </p>
        </div>
        
        <div class="mt-6">
          <button
            @click="loadUserPosts"
            :disabled="loadingPosts"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50"
          >
            
          </button>
        </div>
        
        <div v-if="posts.length > 0" class="mt-6">
          <h2 class="text-xl font-semibold mb-4">Posts ()</h2>
          <div class="space-y-2">
            <div
              v-for="post in posts"
              :key="post.id"
              class="p-3 bg-gray-50 rounded border"
            >
              <h3 class="font-medium"></h3>
              <p class="text-sm text-gray-600"></p>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <div v-else class="text-center">
      <p>User not found</p>
    </div>
  </div>
</template>

<script setup>
import { User } from '~/models/user.model';

const route = useRoute();
const userId = route.params.id;

// Server-side data fetching with error handling
const { data: user, pending, error } = await useLazyAsyncData(`user-${userId}`, async () => {
  try {
    const userInstance = new User();
    await userInstance.load(userId);
    return userInstance.toJSON();
  } catch (err) {
    throw createError({
      statusCode: 404,
      statusMessage: 'User not found'
    });
  }
});

// Client-side posts loading
const posts = ref([]);
const loadingPosts = ref(false);

const loadUserPosts = async () => {
  if (loadingPosts.value) return;
  
  loadingPosts.value = true;
  try {
    const userInstance = new User();
    await userInstance.load(userId);
    const userPosts = await userInstance.loadHasMany('posts');
    posts.value = userPosts.map(post => post.toJSON());
  } catch (error) {
    console.error('Error loading posts:', error);
  } finally {
    loadingPosts.value = false;
  }
};

// Utility function
const formatDate = (dateString) => {
  return new Date(dateString).toLocaleDateString();
};

// SEO meta tags
useSeoMeta({
  title: () => user.value ? `${user.value.name} - User Profile` : 'User Profile',
  description: () => user.value ? `Profile page for ${user.value.name}` : 'User profile page',
});
</script>

Components

<!-- components/UserForm.vue -->
<template>
  <form @submit.prevent="submitForm" class="bg-white p-6 rounded-lg shadow-md">
    <h2 class="text-xl font-semibold mb-4">Add New User</h2>
    
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
      <div>
        <label for="name" class="block text-sm font-medium text-gray-700 mb-1">
          Name *
        </label>
        <input
          id="name"
          v-model="form.name"
          type="text"
          required
          class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
      </div>
      
      <div>
        <label for="email" class="block text-sm font-medium text-gray-700 mb-1">
          Email *
        </label>
        <input
          id="email"
          v-model="form.email"
          type="email"
          required
          class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
      </div>
    </div>
    
    <div class="mt-4">
      <label for="bio" class="block text-sm font-medium text-gray-700 mb-1">
        Bio
      </label>
      <textarea
        id="bio"
        v-model="form.bio"
        rows="3"
        class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
        placeholder="Tell us about yourself..."
      ></textarea>
    </div>
    
    <div class="mt-6">
      <button
        type="submit"
        :disabled="loading"
        class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        
      </button>
    </div>
    
    <div v-if="error" class="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
      
    </div>
  </form>
</template>

<script setup>
import { User } from '~/models/user.model';

const emit = defineEmits(['user-created']);

const form = reactive({
  name: '',
  email: '',
  bio: ''
});

const loading = ref(false);
const error = ref('');

const submitForm = async () => {
  if (loading.value) return;
  
  loading.value = true;
  error.value = '';
  
  try {
    const user = new User();
    user.name = form.name;
    user.email = form.email;
    user.bio = form.bio;
    user.createdAt = new Date().toISOString();
    
    await user.save();
    
    emit('user-created', user.toJSON());
    
    // Reset form
    form.name = '';
    form.email = '';
    form.bio = '';
    
  } catch (err) {
    error.value = err.message || 'Failed to create user';
  } finally {
    loading.value = false;
  }
};
</script>
<!-- components/UserCard.vue -->
<template>
  <div class="bg-white p-4 rounded-lg shadow-md border border-gray-200">
    <div class="flex items-start justify-between">
      <div class="flex-1">
        <h3 class="text-lg font-semibold text-gray-900"></h3>
        <p class="text-sm text-gray-600"></p>
        <p v-if="user.bio" class="text-sm text-gray-500 mt-2"></p>
        <p class="text-xs text-gray-400 mt-2">
          Created: 
        </p>
      </div>
      
      <div class="flex-shrink-0 ml-4">
        <button
          @click="toggleMenu"
          class="text-gray-400 hover:text-gray-600"
        >
          <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
            <path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"></path>
          </svg>
        </button>
      </div>
    </div>
    
    <div v-if="showMenu" class="mt-4 pt-4 border-t border-gray-200">
      <div class="flex space-x-2">
        <button
          @click="loadPosts"
          :disabled="loadingPosts"
          class="text-sm text-blue-600 hover:text-blue-800 disabled:opacity-50"
        >
          
        </button>
        
        <button
          @click="editUser"
          class="text-sm text-gray-600 hover:text-gray-800"
        >
          Edit
        </button>
        
        <button
          @click="deleteUser"
          class="text-sm text-red-600 hover:text-red-800"
        >
          Delete
        </button>
      </div>
    </div>
    
    <div v-if="posts.length > 0" class="mt-4 pt-4 border-t border-gray-200">
      <h4 class="text-sm font-medium text-gray-900 mb-2">
        Posts ()
      </h4>
      <div class="space-y-1">
        <div
          v-for="post in posts"
          :key="post.id"
          class="text-xs text-gray-600 p-2 bg-gray-50 rounded"
        >
          
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { User } from '~/models/user.model';

const props = defineProps({
  user: {
    type: Object,
    required: true
  }
});

const emit = defineEmits(['user-deleted']);

const showMenu = ref(false);
const posts = ref([]);
const loadingPosts = ref(false);

const toggleMenu = () => {
  showMenu.value = !showMenu.value;
};

const loadPosts = async () => {
  if (loadingPosts.value) return;
  
  loadingPosts.value = true;
  try {
    const user = new User();
    await user.load(props.user.id);
    const userPosts = await user.loadHasMany('posts');
    posts.value = userPosts.map(post => post.toJSON());
  } catch (error) {
    console.error('Error loading posts:', error);
  } finally {
    loadingPosts.value = false;
  }
};

const editUser = () => {
  // Navigate to edit page or open modal
  navigateTo(`/users/${props.user.id}/edit`);
};

const deleteUser = async () => {
  if (!confirm('Are you sure you want to delete this user?')) return;
  
  try {
    const user = new User();
    await user.load(props.user.id);
    await user.destroy();
    
    emit('user-deleted', props.user.id);
  } catch (error) {
    console.error('Error deleting user:', error);
    alert('Failed to delete user');
  }
};

const formatDate = (dateString) => {
  return new Date(dateString).toLocaleDateString();
};
</script>

Composables

Create reusable composables for Firebase ORM operations:

// composables/useFirebaseORM.ts
import type { Ref } from 'vue';

export function useFirebaseORM<T extends any>(
  ModelClass: new () => T,
  options: {
    realtime?: boolean;
    initialData?: T[];
  } = {}
) {
  const data: Ref<T[]> = ref(options.initialData || []);
  const loading = ref(false);
  const error = ref<string | null>(null);

  // Load all data
  const loadData = async () => {
    loading.value = true;
    error.value = null;
    
    try {
      const items = await (ModelClass as any).getAll();
      data.value = items;
      return items;
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'An error occurred';
      throw err;
    } finally {
      loading.value = false;
    }
  };

  // Create item
  const createItem = async (itemData: Partial<T>) => {
    try {
      const item = new ModelClass();
      item.initFromData(itemData);
      await (item as any).save();
      
      if (!options.realtime) {
        data.value.push(item);
      }
      
      return item;
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'Failed to create item';
      throw err;
    }
  };

  // Update item
  const updateItem = async (id: string, updates: Partial<T>) => {
    try {
      const item = new ModelClass();
      await (item as any).load(id);
      item.initFromData(updates);
      await (item as any).save();
      
      if (!options.realtime) {
        const index = data.value.findIndex((d: any) => d.getId() === id);
        if (index >= 0) {
          data.value[index] = item;
        }
      }
      
      return item;
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'Failed to update item';
      throw err;
    }
  };

  // Delete item
  const deleteItem = async (id: string) => {
    try {
      const item = new ModelClass();
      await (item as any).load(id);
      await (item as any).destroy();
      
      if (!options.realtime) {
        data.value = data.value.filter((d: any) => d.getId() !== id);
      }
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'Failed to delete item';
      throw err;
    }
  };

  // Set up real-time subscription
  onMounted(() => {
    if (options.realtime && process.client) {
      const unsubscribe = (ModelClass as any).onList((item: T) => {
        const index = data.value.findIndex((d: any) => d.getId() === (item as any).getId());
        if (index >= 0) {
          data.value[index] = item;
        } else {
          data.value.push(item);
        }
      });

      onUnmounted(() => {
        if (unsubscribe) {
          unsubscribe();
        }
      });
    }
  });

  return {
    data: readonly(data),
    loading: readonly(loading),
    error: readonly(error),
    loadData,
    createItem,
    updateItem,
    deleteItem,
  };
}

Using Composables

<!-- pages/users-with-composable.vue -->
<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold mb-6">Users (with Composable)</h1>
    
    <div v-if="loading" class="text-center">Loading...</div>
    <div v-else-if="error" class="text-red-600">Error: </div>
    <div v-else>
      <UserForm @user-created="handleCreateUser" />
      
      <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
        <UserCard
          v-for="user in users"
          :key="user.getId()"
          :user="user.toJSON()"
          @user-deleted="handleDeleteUser"
        />
      </div>
    </div>
  </div>
</template>

<script setup>
import { User } from '~/models/user.model';

const {
  data: users,
  loading,
  error,
  loadData,
  createItem,
  deleteItem
} = useFirebaseORM(User, { realtime: true });

// Load initial data
await loadData();

const handleCreateUser = async (userData) => {
  try {
    await createItem({
      ...userData,
      createdAt: new Date().toISOString()
    });
  } catch (error) {
    console.error('Failed to create user:', error);
  }
};

const handleDeleteUser = async (userId) => {
  try {
    await deleteItem(userId);
  } catch (error) {
    console.error('Failed to delete user:', error);
  }
};
</script>

API Routes

Nuxt 3 Server API

// server/api/users/index.get.ts
import { User } from '~/models/user.model';

export default defineEventHandler(async (event) => {
  try {
    const query = getQuery(event);
    const limit = Number(query.limit) || 10;
    const offset = Number(query.offset) || 0;
    
    const users = await User.query()
      .limit(limit)
      .startAfter(offset)
      .get();
    
    return {
      users: users.map(user => user.toJSON()),
      pagination: { limit, offset }
    };
  } catch (error) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to fetch users'
    });
  }
});
// server/api/users/index.post.ts
import { User } from '~/models/user.model';

export default defineEventHandler(async (event) => {
  try {
    const body = await readBody(event);
    const { name, email, bio } = body;
    
    if (!name || !email) {
      throw createError({
        statusCode: 400,
        statusMessage: 'Name and email are required'
      });
    }
    
    // Check if user already exists
    const existingUser = await User.findByEmail(email);
    if (existingUser) {
      throw createError({
        statusCode: 409,
        statusMessage: 'User with this email already exists'
      });
    }
    
    const user = new User();
    user.name = name;
    user.email = email;
    user.bio = bio;
    user.createdAt = new Date().toISOString();
    
    await user.save();
    
    return user.toJSON();
  } catch (error) {
    if (error.statusCode) {
      throw error;
    }
    throw createError({
      statusCode: 500,
      statusMessage: 'Failed to create user'
    });
  }
});
// server/api/users/[id].get.ts
import { User } from '~/models/user.model';

export default defineEventHandler(async (event) => {
  try {
    const id = getRouterParam(event, 'id');
    
    if (!id) {
      throw createError({
        statusCode: 400,
        statusMessage: 'User ID is required'
      });
    }
    
    const user = new User();
    await user.load(id);
    
    return user.toJSON();
  } catch (error) {
    throw createError({
      statusCode: 404,
      statusMessage: 'User not found'
    });
  }
});

Middleware

// middleware/firebase-init.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // This middleware ensures Firebase is initialized before navigation
  if (process.client) {
    // Client-side Firebase should be initialized by the plugin
    // You can add additional client-side initialization logic here
  }
});

Error Handling

<!-- error.vue -->
<template>
  <div class="min-h-screen flex items-center justify-center bg-gray-50">
    <div class="max-w-md w-full bg-white shadow-lg rounded-lg p-6">
      <div class="text-center">
        <h1 class="text-6xl font-bold text-gray-900 mb-4"></h1>
        <h2 class="text-xl font-semibold text-gray-700 mb-4">
          
        </h2>
        <p class="text-gray-600 mb-6">
          
        </p>
        <div class="space-y-3">
          <button
            @click="handleError"
            class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600"
          >
            Try Again
          </button>
          <button
            @click="goHome"
            class="w-full bg-gray-500 text-white py-2 px-4 rounded hover:bg-gray-600"
          >
            Go Home
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
const props = defineProps({
  error: Object
});

const getErrorDescription = (statusCode) => {
  switch (statusCode) {
    case 404:
      return 'The page you are looking for could not be found.';
    case 500:
      return 'Something went wrong on our servers.';
    default:
      return 'An unexpected error occurred.';
  }
};

const handleError = () => {
  clearError({ redirect: '/' });
};

const goHome = () => {
  navigateTo('/');
};
</script>

Build and Deployment

Static Generation

For static generation with Firebase ORM:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/users', '/about']
    }
  },
  
  // Generate dynamic routes
  hooks: {
    'nitro:config': async (nitroConfig) => {
      if (nitroConfig.prerender?.routes) {
        // Add dynamic user routes
        const users = await User.getAll();
        const userRoutes = users.map(user => `/users/${user.getId()}`);
        nitroConfig.prerender.routes.push(...userRoutes);
      }
    }
  }
});

Environment-specific Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  // Use different configurations per environment
  ...(process.env.NODE_ENV === 'development' && {
    ssr: true,
    // Development-specific settings
  }),
  
  ...(process.env.NODE_ENV === 'production' && {
    // Production optimizations
    experimental: {
      payloadExtraction: false
    }
  })
});

Best Practices

1. Performance Optimization

// Use lazy loading for heavy operations
const { pending, data: heavyData } = useLazyAsyncData('heavy-operation', async () => {
  return await performHeavyOperation();
});

2. Error Boundaries

<template>
  <div>
    <Suspense>
      <template #default>
        <UsersList />
      </template>
      <template #fallback>
        <div>Loading users...</div>
      </template>
    </Suspense>
  </div>
</template>

3. Type Safety

// Define proper types for your data
interface UserData {
  id: string;
  name: string;
  email: string;
  bio?: string;
  createdAt: string;
}

const users: Ref<UserData[]> = ref([]);

Common Issues

1. Server-Client Hydration Mismatch

Ensure data consistency between server and client:

// Use proper data serialization
const { data } = await useLazyAsyncData('users', async () => {
  const users = await User.getAll();
  return users.map(user => user.toJSON()); // Ensure serializable data
});

2. Environment Variables

Use proper prefixes for environment variables in Nuxt 3.

3. Firebase Admin in Client Bundle

Ensure Firebase Admin SDK is not bundled for client-side.

Next Steps