dc-auth
v1.2.1
Published
Autenticación universal con Firebase y Zustand para Next.js 15 - Configurable con callbacks
Maintainers
Readme
🔐 dc-auth
Librería de autenticación moderna y universal para Next.js 15 con Firebase y Zustand. Diseñada específicamente para el App Router de Next.js con soporte completo para Server Components y Client Components.
✨ Características
- ✅ Next.js 15 Ready - Totalmente compatible con App Router
- 🔥 Firebase Auth - Autenticación con Email/Password y Google
- 🎯 Type-Safe - TypeScript completo con tipos exportados
- ⚡ SSR Compatible - Inicialización segura en cliente/servidor
- 🪝 Custom Hooks - Hook
useDCAuthcon todas las funciones - 🛡️ Protected Routes - Componente para rutas protegidas
- 🔄 Estado Global - Zustand para manejo de estado optimizado
- ⚙️ Configurable - Múltiples proyectos/entornos (v1.2.0)
- 🎣 Callbacks - onLogin, onLogout, onError (v1.2.0)
- ❌ Error Handling - Manejo de errores integrado (v1.2.0)
- 👤 Update Profile - Actualizar displayName y photoURL (v1.2.0)
- 📦 Zero Config - Solo configura variables de entorno (o usa config personalizada)
📦 Instalación
npm install dc-auth firebase zustand
# o
pnpm add dc-auth firebase zustand
# o
yarn add dc-auth firebase zustand🚀 Configuración
1. Variables de Entorno
Crea un archivo .env.local en la raíz de tu proyecto Next.js:
NEXT_PUBLIC_FIREBASE_API_KEY=tu_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=tu_auth_domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=tu_project_id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=tu_storage_bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=tu_messaging_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=tu_app_id2. Configurar el Provider
⚠️ IMPORTANTE: Para preservar SSR en Next.js 15, usa el patrón recomendado:
✅ Opción 1: Layout Híbrido (Recomendado)
Mantén tu layout principal como Server Component e importa solo el provider de cliente:
// app/layout.tsx (Server Component)
import { ClientProviders } from "dc-auth";
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="es">
<body>
<ClientProviders>
{children}
</ClientProviders>
</body>
</html>
);
}✅ Opción 2: Provider Selectivo
Si solo algunas páginas necesitan autenticación, envuelve solo esas secciones:
// app/layout.tsx (Server Component - SIN provider)
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="es">
<body>{children}</body>
</html>
);
}// app/(authenticated)/layout.tsx (Client layout para rutas privadas)
"use client";
import { DCAuth } from "dc-auth";
export default function AuthenticatedLayout({
children,
}: {
children: React.ReactNode;
}) {
return <DCAuth>{children}</DCAuth>;
}Esta estructura mantiene el SSR en páginas públicas y solo usa Client Components donde es necesario.
📘 Guía Completa de SSR: Lee SSR_GUIDE.md para ejemplos detallados y mejores prácticas.
3. Configuración Avanzada (v1.2.0)
Si necesitas configuración personalizada o callbacks:
// app/layout.tsx
import { ClientProviders } from "dc-auth";
const firebaseConfig = {
apiKey: "AIzaSy...",
authDomain: "my-app.firebaseapp.com",
projectId: "my-app",
// ...resto de config
};
export default function RootLayout({ children }) {
return (
<html>
<body>
<ClientProviders
config={firebaseConfig} // Config personalizada (opcional)
callbacks={{
onLogin: (user) => {
console.log("Usuario logueado:", user.email);
// Sincronizar con backend, analytics, etc.
},
onLogout: () => {
console.log("Usuario cerró sesión");
// Limpiar datos, redirect, etc.
},
onError: (error) => {
console.error("Error de auth:", error);
// Mostrar toast, enviar a Sentry, etc.
},
}}
>
{children}
</ClientProviders>
</body>
</html>
);
}📘 Guía Completa de Uso: Lee USAGE_GUIDE.md para 8+ ejemplos prácticos con callbacks, analytics, Sentry, etc.
📖 Uso
Hook useDCAuth
El hook principal para interactuar con la autenticación:
"use client";
import { useDCAuth } from "dc-auth";
export default function LoginPage() {
const {
user, // Usuario actual (Firebase User | null)
loading, // Estado de carga
error, // Error actual (si hay) - v1.2.0
clearError, // Limpiar error - v1.2.0
isAuthenticated, // Boolean: ¿está autenticado?
loginWithEmail, // Función de login con email
loginWithGoogle, // Función de login con Google
logout, // Función de logout
updateProfile // Actualizar perfil - v1.2.0
} = useDCAuth();
const handleLogin = async () => {
try {
await loginWithEmail("[email protected]", "password123");
console.log("Login exitoso!");
} catch (error) {
console.error("Error:", error);
}
};
const handleGoogleLogin = async () => {
try {
await loginWithGoogle();
console.log("Login con Google exitoso!");
} catch (error) {
console.error("Error:", error);
}
};
if (loading) return <div>Cargando...</div>;
return (
<div>
{isAuthenticated ? (
<div>
<p>Bienvenido {user?.email}</p>
<button onClick={logout}>Cerrar Sesión</button>
</div>
) : (
<div>
<button onClick={handleLogin}>Login con Email</button>
<button onClick={handleGoogleLogin}>Login con Google</button>
</div>
)}
</div>
);
}Componente ProtectedRoute
Protege rutas que requieren autenticación:
"use client";
import { ProtectedRoute } from "dc-auth";
export default function DashboardPage() {
return (
<ProtectedRoute redirectTo="/login">
<div>
<h1>Dashboard Privado</h1>
<p>Solo usuarios autenticados pueden ver esto</p>
</div>
</ProtectedRoute>
);
}Ejemplo de Login Completo
"use client";
import { useDCAuth } from "dc-auth";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const { loginWithEmail, loginWithGoogle, isAuthenticated, loading } = useDCAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
// Redirigir cuando el usuario se autentique
useEffect(() => {
if (!loading && isAuthenticated) {
router.push("/dashboard");
}
}, [isAuthenticated, loading, router]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
try {
await loginWithEmail(email, password);
// La redirección se maneja en el useEffect
} catch (err: any) {
setError(err.message);
}
};
const handleGoogleLogin = async () => {
try {
await loginWithGoogle();
// La redirección se maneja en el useEffect
} catch (err: any) {
setError(err.message);
}
};
if (loading) {
return <div>Cargando...</div>;
}
if (isAuthenticated) {
return null; // Redirigiendo...
}
return (
<div className="login-container">
<h1>Iniciar Sesión</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Contraseña"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Entrar</button>
</form>
<button onClick={handleGoogleLogin}>
Continuar con Google
</button>
{error && <p className="error">{error}</p>}
</div>
);
}Ejemplo de Dashboard Protegido
"use client";
import { ProtectedRoute, useDCAuth } from "dc-auth";
import { useRouter } from "next/navigation";
export default function DashboardPage() {
const { user, logout } = useDCAuth();
const router = useRouter();
const handleLogout = async () => {
await logout();
router.push("/login");
};
return (
<ProtectedRoute redirectTo="/login">
<div className="dashboard">
<header>
<h1>Dashboard</h1>
<div>
<span>{user?.email}</span>
<button onClick={handleLogout}>Cerrar Sesión</button>
</div>
</header>
<main>
<h2>Bienvenido, {user?.displayName || user?.email}</h2>
<p>Este contenido es privado</p>
</main>
</div>
</ProtectedRoute>
);
}Acceso sin Hooks (window.dcAuth)
También puedes acceder a las funciones de autenticación directamente desde window.dcAuth:
"use client";
function MyComponent() {
const handleLogin = () => {
if (typeof window !== "undefined" && window.dcAuth) {
window.dcAuth.loginWithEmail("[email protected]", "password123")
.then(() => console.log("Login exitoso"))
.catch((err) => console.error(err));
}
};
return <button onClick={handleLogin}>Login</button>;
}⚠️ Nota: Este método es útil para casos especiales, pero se recomienda usar el hook
useDCAuthpara mejor experiencia de desarrollo con TypeScript.
🎯 API Reference
useDCAuth()
Hook principal que retorna:
| Propiedad | Tipo | Descripción |
|-----------|------|-------------|
| user | User \| null | Usuario actual de Firebase |
| loading | boolean | Estado de carga de la autenticación |
| isAuthenticated | boolean | true si hay un usuario autenticado |
| loginWithEmail | (email: string, password: string) => Promise<void> | Login con email/password |
| loginWithGoogle | () => Promise<void> | Login con Google |
| logout | () => Promise<void> | Cerrar sesión |
<DCAuth>
Componente Provider que debe envolver tu aplicación.
Props:
children: React.ReactNode- Componentes hijos
<ProtectedRoute>
Componente para proteger rutas que requieren autenticación.
Props:
| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| children | React.ReactNode | - | Contenido protegido |
| redirectTo | string | "/login" | Ruta a la que redirigir si no está autenticado |
Tipos TypeScript
import type { DCAuthConfig, DCAuthState, DCAuthMethods } from "dc-auth";
// DCAuthConfig - Configuración de Firebase
interface DCAuthConfig {
apiKey: string;
authDomain: string;
projectId: string;
storageBucket: string;
messagingSenderId: string;
appId: string;
}
// DCAuthState - Estado de autenticación
interface DCAuthState {
user: User | null;
loading: boolean;
setUser: (user: User | null) => void;
setLoading: (loading: boolean) => void;
}
// DCAuthMethods - Métodos de autenticación
interface DCAuthMethods {
loginWithEmail: (email: string, password: string) => Promise<void>;
loginWithGoogle: () => Promise<void>;
logout: () => Promise<void>;
}🏗️ Arquitectura y SSR
Preservando Server-Side Rendering
dc-auth está diseñado para no romper SSR. Sigue estas prácticas:
✅ Páginas Públicas Permanecen como Server Components
// app/page.tsx - Server Component ✅
export default function HomePage() {
return (
<div>
<h1>Bienvenido</h1>
<p>Esta página se renderiza en el servidor</p>
</div>
);
}✅ Solo Páginas con Auth usan "use client"
// app/dashboard/page.tsx - Client Component (usa auth)
"use client";
import { useDCAuth } from "dc-auth";
export default function DashboardPage() {
const { user } = useDCAuth();
return <div>Hola {user?.email}</div>;
}✅ Usa Route Groups para Organizar
app/
├── layout.tsx # Server Component (root)
├── page.tsx # Server Component (home pública)
├── about/
│ └── page.tsx # Server Component
├── (auth)/ # Route group para páginas con auth
│ ├── layout.tsx # Client Component con DCAuth
│ ├── dashboard/
│ │ └── page.tsx # Client Component
│ └── profile/
│ └── page.tsx # Client Component
└── (public)/
└── pricing/
└── page.tsx # Server ComponentVentajas de este Patrón
- ✅ SEO optimizado - Páginas públicas con SSR completo
- ✅ Performance - Solo carga client JS donde es necesario
- ✅ Flexibilidad - Mezcla server y client components
- ✅ Cacheable - Next.js puede cachear server components
🔧 Características Avanzadas
Manejo de Errores
Todas las funciones de autenticación lanzan errores que puedes capturar:
try {
await loginWithEmail(email, password);
} catch (error) {
if (error.code === "auth/user-not-found") {
console.log("Usuario no encontrado");
} else if (error.code === "auth/wrong-password") {
console.log("Contraseña incorrecta");
} else {
console.log("Error:", error.message);
}
}Persistencia de Sesión
Firebase Auth maneja automáticamente la persistencia de sesión. El usuario permanecerá autenticado entre recargas de página.
SSR y Hydration
La librería está diseñada para Next.js 15 App Router:
- ✅ Inicialización segura solo en cliente
- ✅ Sin errores de hydration
- ✅ Compatible con Server Components (aunque el auth es client-side)
🏗️ Estructura del Proyecto
dc-auth/
├── src/
│ ├── DCAuthProvider.tsx # Provider principal
│ ├── ProtectedRoute.tsx # Componente de rutas protegidas
│ ├── hooks/
│ │ └── useDCAuth.ts # Hook principal
│ ├── store.ts # Store de Zustand
│ ├── types.ts # Tipos TypeScript
│ └── index.ts # Exports públicos
├── dist/ # Build output
├── package.json
├── tsconfig.json
└── tsup.config.ts🤝 Contribuir
Las contribuciones son bienvenidas! Por favor:
- Fork el proyecto
- Crea una rama (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
📝 Changelog
v1.0.0
- ✅ Soporte completo para Next.js 15
- ✅ Autenticación con Firebase (Email/Password y Google)
- ✅ Hook
useDCAuthcon TypeScript - ✅ Componente
ProtectedRoute - ✅ Manejo seguro de SSR
- ✅ Singleton pattern para Firebase
- ✅ Manejo de errores mejorado
🔧 Troubleshooting
Error: "Cannot update a component while rendering a different component"
Si ves este error:
Cannot update a component (Router) while rendering a different component (LoginPage).Causa: Estás llamando a router.push() durante el render, no en un efecto o evento.
Solución: Mueve la redirección a un useEffect:
// ❌ MAL - Durante el render
if (isAuthenticated) {
router.push("/dashboard");
return null;
}
// ✅ BIEN - En useEffect
useEffect(() => {
if (!loading && isAuthenticated) {
router.push("/dashboard");
}
}, [isAuthenticated, loading, router]);Error: "You're importing a component that needs useEffect"
Si ves este error al importar desde dc-auth:
You're importing a component that needs `useEffect`.
This React Hook only works in a Client Component.Solución: Asegúrate de estar usando la versión más reciente de dc-auth:
npm update dc-auth
# o
npm install dc-auth@latestLa versión 1.0.1+ incluye la directiva "use client" en los bundles compilados.
Error: "Firebase app already exists"
Si ves este error, es probable que estés inicializando Firebase múltiples veces. Asegúrate de:
- Usar
ClientProvidersoDCAuthsolo una vez en tu árbol de componentes - No inicializar Firebase manualmente en otro lugar
- Usar la versión 1.0.0+ que incluye el singleton pattern
Páginas no tienen SSR
Si tus páginas públicas no tienen SSR:
- Verifica que tu
app/layout.tsxno tenga"use client" - Usa
ClientProvidersen lugar deDCAuthen el layout raíz - Las páginas que no usan
useDCAuthdeben ser Server Components (sin"use client")
Lee SSR_GUIDE.md para más detalles.
🔮 Roadmap y Escalabilidad
Esta librería está en desarrollo activo. Hemos identificado áreas clave de mejora para hacerla production-ready.
📊 Análisis Completo
- EXECUTIVE_SUMMARY.md - Resumen ejecutivo y recomendaciones
- SCALABILITY_ANALYSIS.md - Análisis detallado de limitaciones y mejoras
- FEATURE_COMPARISON.md - Comparación con NextAuth, Clerk y Supabase
- IMPLEMENTATION_PLAN.md - Plan técnico para v1.1.0
🎯 Próximas Features (v1.1.0)
- [ ] Registro de usuarios (
signUp) - [ ] Reset de contraseña
- [ ] Verificación de email
- [ ] Actualización de perfil
- [ ] Provider configurable con callbacks
- [ ] Error state mejorado
- [ ] Más providers (GitHub, Facebook, Twitter)
- [ ] Hooks especializados
💡 Contribuciones
¿Quieres contribuir? Lee CONTRIBUTING.md y IMPLEMENTATION_PLAN.md.
📄 Licencia
MIT © Kevin Del Cid
🔗 Links
Hecho con ❤️ para la comunidad Next.js
