Next.js 14+ App Router integration with Firebase including server/client SDK separation and data fetching patterns. PROACTIVELY activate for: (1) setting up dual SDK architecture for client/server, (2) fetching data in Server Components with Admin SDK, (3) implementing real-time listeners in Client Components. Triggers: "nextjs firebase", "app router", "server client sdk"
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: firebase-nextjs-integration-strategies
version: "1.0"
description: >
Next.js 14+ App Router integration with Firebase including server/client SDK separation and data fetching patterns.
PROACTIVELY activate for: (1) setting up dual SDK architecture for client/server, (2) fetching data in Server Components with Admin SDK, (3) implementing real-time listeners in Client Components.
Triggers: "nextjs firebase", "app router", "server client sdk"
group: data
core-integration:
techniques:
primary: ["structured_decomposition"]
secondary: []
contracts:
input: "none"
output: "none"
patterns: "none"
rubrics: "none"
---
# Firebase Next.js Integration Strategies
## Overview
Next.js 14+ App Router has a clear server/client boundary. This skill provides patterns for integrating Firebase's dual SDK model (Client SDK + Admin SDK) with Next.js execution environments.
## Dual SDK Architecture
### Client SDK (Client Components, Browser)
**Use For**:
- Client-side authentication (sign-in, sign-up, sign-out)
- Real-time listeners (`onSnapshot`)
- Client-side mutations with optimistic updates
- File uploads to Storage
**Location**: `src/lib/firebase/client.ts`
```typescript
import { initializeApp, getApps } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
```
### Admin SDK (Server Components, API Routes, Server Actions)
**Use For**:
- Privileged data access in Server Components
- Token verification in middleware
- Server-side mutations
- Bypassing security rules
**Location**: `src/lib/firebase/admin.ts`
```typescript
import 'server-only'; // CRITICAL: Prevents client bundling
import { initializeApp, getApps, cert } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';
const adminConfig = {
projectId: process.env.FIREBASE_ADMIN_PROJECT_ID,
credential: cert({
projectId: process.env.FIREBASE_ADMIN_PROJECT_ID,
clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
};
const adminApp = getApps().length === 0 ? initializeApp(adminConfig, 'admin') : getApps()[0];
export const adminAuth = getAuth(adminApp);
export const adminDb = getFirestore(adminApp);
```
## Data Fetching Patterns
### Pattern 1: Server Component (Recommended for Initial Load)
**Advantages**:
- Fast (no client-side fetch)
- SEO-friendly (pre-rendered)
- Secure (uses Admin SDK)
- Cached by Next.js
```typescript
// app/posts/page.tsx
import { adminDb } from '@/lib/firebase/admin';
import { postAdminConverter, type Post } from '@/lib/firebase/schemas/post.schema';
export default async function PostsPage() {
// Fetch data server-side with Admin SDK
const postsSnapshot = await adminDb
.collection('posts')
.withConverter(postAdminConverter)
.where('status', '==', 'published')
.orderBy('createdAt', 'desc')
.limit(10)
.get();
const posts: Post[] = postsSnapshot.docs.map(doc => doc.data());
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
```
### Pattern 2: Client Component with Real-Time Listener
**Use When**:
- Need real-time updates
- User-specific data (respects security rules)
- Interactive UI
```typescript
// components/CommentsList.tsx
'use client';
import { useEffect, useState } from 'react';
import { collection, query, where, onSnapshot, orderBy } from 'firebase/firestore';
import { db } from '@/lib/firebase/client';
import { commentConverter, type Comment } from '@/lib/firebase/schemas/comment.schema';
export function CommentsList({ postId }: { postId: string }) {
const [comments, setComments] = useState<Comment[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Real-time listener with withConverter
const q = query(
collection(db, 'comments'),
where('postId', '==', postId),
orderBy('createdAt', 'desc')
).withConverter(commentConverter);
const unsubscribe = onSnapshot(q, (snapshot) => {
const commentsData = snapshot.docs.map(doc => doc.data());
setComments(commentsData);
setLoading(false);
});
return unsubscribe; // Cleanup on unmount
}, [postId]);
if (loading) return <div>Loading comments...</div>;
return (
<div>
{comments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
);
}
```
### Pattern 3: Server Action (Mutations)
**Use For**:
- Form submissions
- User-initiated mutations
- Progressive enhancement
```typescript
// app/actions/createPost.ts
'use server';
import { adminDb } from '@/lib/firebase/admin';
import { PostSchema, postAdminConverter } from '@/lib/firebase/schemas/post.schema';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const CreatePostInput = PostSchema.pick({
title: true,
content: true,
}).extend({
authorId: z.string(),
});
export async function createPost(input: z.infer<typeof CreatePostInput>) {
// Validate input
const validated = CreatePostInput.parse(input);
// Create post with Admin SDK
const postRef = adminDb.collection('posts').doc();
await postRef.withConverter(postAdminConverter).set({
...validated,
status: 'draft',
viewCount: 0,
createdAt: new Date(),
updatedAt: new Date(),
});
// Revalidate posts page cache
revalidatePath('/posts');
return { success: true, postId: postRef.id };
}
```
## Decision Tree
**When to use Server Components**:
- Initial page load
- SEO-critical content
- Privileged data access
- Static or slow-changing data
**When to use Client Components**:
- Real-time updates
- User interactions
- Client-side state
- Animations
**When to use Server Actions**:
- Form submissions
- Mutations
- Progressive enhancement
**When to use API Routes**:
- External webhooks
- Complex server logic
- Non-Next.js clients
## Best Practices
**Do**:
- Use Server Components for initial data load (faster, SEO-friendly)
- Use Client Components for real-time updates
- Use Admin SDK in Server Components/Actions (privileged, bypasses rules)
- Use Client SDK in Client Components (respects security rules)
- Verify tokens in middleware
- Store tokens in `HttpOnly` cookies (not localStorage)
- Use `server-only` package for admin files
**Don't**:
- Import Admin SDK in Client Components
- Use Client SDK in Server Components (wrong execution context)
- Skip token verification
- Fetch data client-side when Server Component would work
- Forget to cleanup real-time listeners (`onSnapshot`)
---
**Related Skills**: `firebase-admin-sdk-server-integration`, `firestore-data-modeling-patterns`
No comments yet. Be the first to comment!