@djangocfg/layouts
Simple, straightforward layout components for Next.js - import and use with props.
Part of DjangoCFG — modern Django framework for production-ready SaaS applications.
Install
pnpm add @djangocfg/layouts
Core Components
BaseApp
Core providers wrapper - use when you need providers without layout routing:
import { BaseApp } from '@djangocfg/layouts';
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<BaseApp
theme={{ defaultTheme: 'dark' }}
analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
centrifugo={{ enabled: true, url: process.env.NEXT_PUBLIC_CENTRIFUGO_URL }}
pwaInstall={{ enabled: true, showInstallHint: true }}
mcpChat={{ enabled: true, autoDetectEnvironment: true }}
>
{children}
</BaseApp>
</body>
</html>
);
}
Included providers:
- ThemeProvider - Light/dark/system theme management
- TooltipProvider - Tooltip positioning context
- SWRConfig - Data fetching configuration
- AuthProvider - Authentication context (from
@djangocfg/api)
- AnalyticsProvider - Google Analytics (optional)
- CentrifugoProvider - WebSocket real-time (optional)
- PwaProvider - PWA installation (optional)
- ErrorTrackingProvider - Error handling and tracking
- ErrorBoundary - React error boundary
- MCP Chat Widget - AI chat assistant (optional)
Global components:
- PageProgress - NProgress bar for route changes
- Toaster - Toast notifications container
- A2HSHint - PWA install hint (if enabled)
Note: Auth functionality is provided by @djangocfg/api package. See @djangocfg/api documentation for auth usage.
AppLayout
Smart layout router built on BaseApp - automatically selects layout based on route:
import { AppLayout } from '@djangocfg/layouts';
import { PublicLayout } from './_layouts/PublicLayout';
import { PrivateLayout } from './_layouts/PrivateLayout';
import { AdminLayout } from './_layouts/AdminLayout';
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<AppLayout
// Provider configs (passed to BaseApp)
theme={{ defaultTheme: 'system' }}
analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
pwaInstall={{ enabled: true }}
// Layout components
publicLayout={{
component: PublicLayout,
enabledPath: ['/', '/legal', '/contact']
}}
privateLayout={{
component: PrivateLayout,
enabledPath: ['/dashboard', '/profile']
}}
adminLayout={{
component: AdminLayout,
enabledPath: '/admin'
}}
// Skip layout for fullscreen pages (providers still applied)
noLayoutPaths={['/private/terminal', '/embed']}
>
{children}
</AppLayout>
</body>
</html>
);
}
Layout priority: Admin → Private → Public → Fallback
noLayoutPaths: Paths that render without any layout wrapper. Useful for fullscreen pages (terminal, embed, print). Providers (auth, theme, centrifugo) are still applied.
Layouts
Simple, props-based layout components. No complex configs needed!
Available Layouts
import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
<PublicLayout
logo="/logo.svg"
siteName="My App"
navigation={navItems}
>
{children}
</PublicLayout>
<PrivateLayout
sidebar={{
groups: [
{ label: 'Main', items: menuItems }
]
}}
header={{ title: 'Dashboard' }}
>
{children}
</PrivateLayout>
<AuthLayout
logo="/logo.svg"
title="Sign In"
subtitle="Welcome back"
>
<LoginForm />
</AuthLayout>
BaseApp | Core providers wrapper (Theme, Auth, SWR, ErrorTracking, Toaster) |
AppLayout | Smart layout router with route-based layout switching |
PublicLayout | Public pages (home, docs, contact, legal) |
PrivateLayout | Authenticated user pages (dashboard, profile) |
AuthLayout | Authentication pages (login, signup, password reset) |
AdminLayout | Admin panel layout |
ProfileLayout | User profile pages |
i18n Support
Pass i18n config to AppLayout for locale switching in all layouts:
import { AppLayout } from '@djangocfg/layouts';
import { useLocaleSwitcher } from '@djangocfg/nextjs/i18n/client';
function RootLayout({ children }) {
const { locale, locales, changeLocale } = useLocaleSwitcher();
return (
<AppLayout
// ... other configs
i18n={{
locale,
locales,
onLocaleChange: changeLocale,
}}
>
{children}
</AppLayout>
);
}
The LocaleSwitcher component will automatically appear in PublicLayout and PrivateLayout headers.
LocaleSwitcher Component
A presentational locale switcher component that can be used standalone:
import { LocaleSwitcher } from '@djangocfg/layouts';
<LocaleSwitcher
locale="en"
locales={['en', 'ru', 'ko']}
onChange={(locale) => router.push(`/${locale}`)}
/>
<LocaleSwitcher
locale={currentLocale}
locales={['en', 'ru', 'ko']}
onChange={handleLocaleChange}
labels={{ en: 'English', ru: 'Русский', ko: '한국어' }}
variant="outline"
size="sm"
showIcon={true}
/>
Props:
locale | string | - | Current locale code |
locales | string[] | - | Available locale codes |
onChange | (locale: string) => void | - | Callback when locale changes |
labels | Record<string, string> | Built-in | Custom labels for locales |
showCode | boolean | false | Show locale code with label |
variant | 'ghost' | 'outline' | 'default' | 'ghost' | Button variant |
size | 'sm' | 'default' | 'lg' | 'icon' | 'sm' | Button size |
showIcon | boolean | true | Show globe icon |
className | string | - | Custom CSS class |
Smart version: For automatic locale detection with next-intl hooks, use @djangocfg/nextjs/i18n/components which wraps this component.
Extension Layouts: Additional layouts like SupportLayout and PaymentsLayout are available in extension packages:
@djangocfg/ext-support - Support ticket layouts
@djangocfg/ext-payments - Payment flow layouts
Analytics
Google Analytics integration via react-ga4. Auto-tracks pageviews and user sessions.
Setup
Add tracking ID to your config:
export const appLayoutConfig: AppLayoutConfig = {
analytics: {
googleTrackingId: 'G-XXXXXXXXXX',
},
};
Analytics is automatically initialized by AppLayout. Works only in production (NODE_ENV === 'production').
Usage
import { useAnalytics, Analytics, AnalyticsEvent, AnalyticsCategory } from '@djangocfg/layouts';
const { event, isEnabled } = useAnalytics();
event(AnalyticsEvent.THEME_CHANGE, {
category: AnalyticsCategory.ENGAGEMENT,
label: 'dark',
});
Analytics.event('button_click', { category: 'engagement', label: 'signup' });
Analytics.setUser('user-123');
Predefined Events
| Auth | AUTH_LOGIN_SUCCESS, AUTH_LOGOUT, AUTH_SESSION_EXPIRED, AUTH_TOKEN_REFRESH |
| OAuth | AUTH_OAUTH_START, AUTH_OAUTH_SUCCESS, AUTH_OAUTH_FAIL |
| Error | ERROR_BOUNDARY, ERROR_API, ERROR_VALIDATION, ERROR_NETWORK |
| Navigation | NAV_ADMIN_ENTER, NAV_DASHBOARD_ENTER, NAV_PAGE_VIEW |
| Engagement | THEME_CHANGE, SIDEBAR_TOGGLE, MOBILE_MENU_OPEN |
Auto-tracking
Built-in tracking for:
- Page views - on every route change
- User ID - automatically set when user is authenticated
- Auth events - login, logout, session expiry (from
@djangocfg/api)
- OAuth events - GitHub OAuth start, success, failure
- Errors - React ErrorBoundary errors
PWA Installation
Enable PWA installation prompts with pwaInstall config:
import { BaseApp } from '@djangocfg/layouts';
<BaseApp
pwaInstall={{
enabled: true,
showInstallHint: true, // Show A2HS hint
resetAfterDays: 3, // Re-show after dismissal
delayMs: 1000, // Delay before showing
logo: '/logo192.png', // PWA logo
resumeLastPage: true, // Resume last page on PWA launch
}}
>
{children}
</BaseApp>
Features:
- A2HSHint - Platform-specific install hints (iOS Safari/Chrome/Firefox, Android Chrome, Desktop)
- Page Resume - Automatically navigate to last viewed page when PWA is launched
- Auto-detection - Detects if running as PWA
- Dismissal tracking - Respects user dismissal with localStorage
- Custom timing - Configurable delay and reset periods
Page Resume:
When resumeLastPage: true, the app saves the current pathname on every navigation and restores it when the PWA is launched. Pages like /auth, /login, /error are automatically excluded. Data expires after 24 hours.
Usage
import { usePwa } from '@djangocfg/layouts/snippets';
const { isPWA, isInstallable } = usePwa();
Snippets
Reusable UI components and hooks ready to use.
import {
Breadcrumbs,
AuthDialog,
usePwa,
} from '@djangocfg/layouts/snippets';
Breadcrumbs | Navigation breadcrumbs with automatic path generation |
AuthDialog | Auth modal (login/register) with event-based triggers |
AnalyticsProvider | Analytics wrapper component |
usePwa | PWA status hook (isPWA, isInstallable, etc.) |
usePWAPageResume | Resume last page on PWA launch |
A2HSHint | Add to Home Screen hint component |
PWAPageResumeManager | Component for PWA page resume (use via BaseApp config) |
Extension Snippets: Additional components are available in extension packages:
@djangocfg/ext-leads - ContactForm, ContactPage, ContactInfo
@djangocfg/ext-knowbase - KnowledgeChat, ChatWidget, ChatUIProvider
@djangocfg/ext-newsletter - Hero (with newsletter subscription)
Breadcrumbs
import Breadcrumbs from '@djangocfg/layouts/snippets';
<Breadcrumbs />
<Breadcrumbs
items={[
{ path: '/', label: 'Home', isActive: false },
{ path: '/products', label: 'Products', isActive: true },
]}
/>
AuthDialog
import { AuthDialog, openAuthDialog } from '@djangocfg/layouts/snippets';
<AuthDialog authPath="/auth" />
openAuthDialog({ message: 'Sign in to continue' });
Components
Utility components organized by category.
Core Components
import {
JsonLd,
LucideIcon,
PageProgress,
Suspense
} from '@djangocfg/layouts/components/core';
JsonLd | JSON-LD structured data component |
LucideIcon | Lucide icon wrapper component |
PageProgress | Page loading progress indicator |
Suspense | Suspense wrapper component |
Error Components
import {
ErrorBoundary,
ErrorLayout,
getErrorContent,
ERROR_CODES
} from '@djangocfg/layouts/components/errors';
ErrorBoundary | React error boundary component |
ErrorLayout | Reusable error page layout (404, 500, etc.) |
getErrorContent | Get error content by status code |
ERROR_CODES | Common HTTP error code constants |
ErrorLayout Usage:
import { ErrorLayout } from '@djangocfg/layouts/components/errors';
export default function NotFound() {
return <ErrorLayout code={404} supportEmail="support@example.com" />;
}
'use client';
import { ErrorLayout } from '@djangocfg/layouts/components/errors';
export default function Error({ error, reset }) {
return <ErrorLayout code={500} supportEmail="support@example.com" />;
}
Redirect Component
import { RedirectPage } from '@djangocfg/layouts/components/RedirectPage';
export default function Page() {
return (
<RedirectPage
authenticatedPath="/dashboard"
unauthenticatedPath="/auth"
loadingText="Loading..."
/>
);
}
Error Tracking
import {
ErrorTrackingProvider,
useErrors,
ErrorButtons,
ErrorToast
} from '@djangocfg/layouts/components/errors/ErrorsTracker';
<ErrorTrackingProvider>
<YourApp />
</ErrorTrackingProvider>
const { addError, clearErrors, errors } = useErrors();
Update Notifier
import { UpdateNotifier } from '@djangocfg/layouts/components/UpdateNotifier';
<UpdateNotifier />
Pages
Ready-to-use page components.
Legal Pages
Pre-built legal page components with default configurations.
import {
PrivacyPage,
TermsPage,
CookiesPage,
SecurityPage
} from '@djangocfg/layouts/pages/legal';
export default PrivacyPage;
import { PrivacyPage, privacyConfig } from '@djangocfg/layouts/pages/legal';
export default function CustomPrivacy() {
return <PrivacyPage config={{
...privacyConfig,
lastUpdated: '2024-01-01',
}} />;
}
PrivacyPage | Privacy policy page |
TermsPage | Terms of service page |
CookiesPage | Cookie policy page |
SecurityPage | Security policy page |
Utils
Utility functions and helpers.
import {
generateOgImageUrl,
getAbsoluteOgImageUrl,
createOgImageUrlBuilder
} from '@djangocfg/layouts/utils/og-image';
const ogUrl = generateOgImageUrl('/api/og', {
title: 'My Page',
description: 'Page description',
siteName: 'My Site',
});
generateOgImageUrl | Generate OG image URL with base64 encoding |
getAbsoluteOgImageUrl | Get absolute OG image URL |
createOgImageUrlBuilder | Create OG image URL builder with defaults |
Exports
@djangocfg/layouts | Main exports (all modules) |
@djangocfg/layouts/layouts | Layout components |
@djangocfg/layouts/snippets | Reusable components + Analytics |
@djangocfg/layouts/components | All utility components |
@djangocfg/layouts/pages | Page components (legal pages) |
@djangocfg/layouts/pages/legal | Legal page components |
@djangocfg/layouts/utils | Utilities (og-image, logger) |
@djangocfg/layouts/styles | CSS |
@djangocfg/layouts/styles/dashboard | Dashboard-specific CSS |
Auth Exports: For authentication, use @djangocfg/api/auth - See @djangocfg/api documentation
Extension Packages
Additional functionality is available in extension packages:
| Newsletter | @djangocfg/ext-newsletter | Newsletter subscription and campaigns |
| Knowledge Base | @djangocfg/ext-knowbase | Documentation, chat, RAG-powered AI |
| Leads | @djangocfg/ext-leads | Lead capture and contact forms |
| Payments | @djangocfg/ext-payments | Payment processing and subscriptions |
| Support | @djangocfg/ext-support | Support tickets and helpdesk |
Each extension has its own layouts, contexts, and components. See individual extension documentation for details.
Requirements
- Next.js >= 15
- React >= 19
- Tailwind CSS >= 4
- react-ga4 (bundled)
- @djangocfg/ui-nextjs (peer dependency)
- @djangocfg/api (peer dependency)
Philosophy
This package follows a simple, props-based approach:
- ✅ No complex configs - just pass props
- ✅ Type-safe - full TypeScript support
- ✅ Flexible - compose layouts as needed
- ✅ Production-ready - built for real apps
- ✅ Modular - core layouts in one package, extensions separate
Examples
Complete App Setup
import { AppLayout } from '@djangocfg/layouts';
import { appLayoutConfig } from './_config/appLayoutConfig';
export default function RootLayout({ children }) {
return (
<AppLayout config={appLayoutConfig}>
{children}
</AppLayout>
);
}
Public Page
import { PublicLayout } from '@djangocfg/layouts';
export default function HomePage() {
return (
<PublicLayout
logo="/logo.svg"
siteName="My App"
navigation={[
{ label: 'Home', href: '/' },
{ label: 'Docs', href: '/docs' },
{ label: 'Contact', href: '/contact' }
]}
>
<h1>Welcome</h1>
</PublicLayout>
);
}
Private Dashboard
import { PrivateLayout } from '@djangocfg/layouts';
export default function DashboardPage() {
return (
<PrivateLayout
sidebar={{
groups: [
{
label: 'Main',
items: [
{ label: 'Dashboard', href: '/dashboard', icon: 'LayoutDashboard' },
{ label: 'Settings', href: '/settings', icon: 'Settings' }
]
}
],
homeHref: '/dashboard'
}}
header={{ title: 'Dashboard' }}
>
<h1>Dashboard</h1>
</PrivateLayout>
);
}
Auth Page (OTP + 2FA Authentication)
'use client';
import { AuthLayout } from '@djangocfg/layouts';
export default function AuthPage() {
return (
<AuthLayout
sourceUrl="https://example.com"
supportUrl="/support"
termsUrl="/terms"
privacyUrl="/privacy"
enablePhoneAuth={false}
enableGithubAuth={true}
logoUrl="/logo.svg"
redirectUrl="/dashboard"
onOTPSuccess={() => {
console.log('Authentication successful');
}}
onOAuthSuccess={(user, isNewUser, provider) => {
console.log('OAuth success:', { user, isNewUser, provider });
}}
>
<div className="text-center mb-6">
<h2 className="text-2xl font-bold">Welcome to My App</h2>
<p className="text-muted-foreground mt-2">
Sign in with your email or phone
</p>
</div>
</AuthLayout>
);
}
Authentication Flow:
- Identifier → Enter email/phone or click GitHub OAuth
- OTP → Enter 6-digit verification code
- 2FA → Enter TOTP code (if 2FA enabled for user)
- Success → Show logo animation, then redirect
AuthLayout Props:
sourceUrl | string | Application URL for OTP emails |
redirectUrl | string | URL to redirect after successful auth (default: /dashboard) |
logoUrl | string | Logo URL for success screen (SVG recommended) |
enablePhoneAuth | boolean | Enable phone number authentication |
enableGithubAuth | boolean | Enable GitHub OAuth |
enable2FASetup | boolean | Enable 2FA setup prompt after login (default: true). Set to false to skip 2FA setup prompt - users can then configure 2FA from ProfileLayout instead. |
termsUrl | string | Terms of service URL (shows checkbox if provided) |
privacyUrl | string | Privacy policy URL |
supportUrl | string | Support page URL |
onOTPSuccess | () => void | Callback after successful authentication |
onOAuthSuccess | (user, isNewUser, provider) => void | Callback after successful OAuth |
onError | (message: string) => void | Error callback |
Profile Page (with 2FA Management)
'use client';
import { ProfileLayout } from '@djangocfg/layouts';
export default function ProfilePage() {
return (
<ProfileLayout
title="Profile Settings"
description="Manage your account"
enable2FA={true} // Show 2FA management section
showMemberSince={true}
showLastLogin={true}
onUnauthenticated={() => {
// Redirect to login
}}
/>
);
}
ProfileLayout Props:
title | string | Page title (default: "Profile Settings") |
description | string | Page description |
enable2FA | boolean | Show 2FA management section (default: false). When enabled, users can enable/disable Two-Factor Authentication from their profile. |
showMemberSince | boolean | Show member since date (default: true) |
showLastLogin | boolean | Show last login date (default: true) |
onUnauthenticated | () => void | Callback when user is not authenticated |
2FA Configuration Strategy:
- Use
enable2FASetup={false} in AuthLayout to skip post-login 2FA setup prompt
- Use
enable2FA={true} in ProfileLayout to allow users to manage 2FA from settings
- This gives users control over when to enable 2FA instead of forcing it during login
License
MIT
Links