Back to Blog

GDPR-compliant SaaS: cookie consent and data protection in Next.js

Security
Compliance
Privacy
Next.js
TypeScript
GDPR

Ensuring GDPR compliance for your SaaS application means implementing category-based consent management, privacy-first design patterns, and server-side consent checking. From cookie consent banners to preference management and consent tracking, creating a production-ready GDPR compliance system requires weeks of careful development. Fastack comes with a complete, privacy-first cookie consent package that handles consent management, category-based preferences, and analytics integration—saving you weeks of development time.

Why GDPR compliance matters for SaaS

GDPR compliance is essential for SaaS applications operating in the EU:

  • Legal requirement for EU operations
  • User trust through transparent data handling
  • Category-based consent for granular control
  • Server-side consent checking for reliable enforcement
  • Analytics integration that respects user preferences

What you get with Fastack

Fastack includes a complete GDPR-compliant cookie consent system:

Cookie consent provider

Fastack provides a React context provider that manages cookie consent state throughout your application:

packages/cookies/ui/context/cookie-consent-context.tsx
'use client';

import { createContext, useContext, type ReactNode } from 'react';
import { useCookieConsent } from '../../hooks';
import type { CookieConsentState, CookieConsentActions } from '../../hooks';

interface CookieConsentContextValue extends CookieConsentState, CookieConsentActions {}

const CookieConsentContext = createContext<CookieConsentContextValue | undefined>(undefined);

interface CookieConsentProviderProps {
    children: ReactNode;
}

export function CookieConsentProvider({ children }: CookieConsentProviderProps) {
    const consentState = useCookieConsent();

    return (
        <CookieConsentContext.Provider value={consentState}>
            {children}
        </CookieConsentContext.Provider>
    );
}

export function useCookieConsentContext(): CookieConsentContextValue {
    const context = useContext(CookieConsentContext);
    if (context === undefined) {
        throw new Error('useCookieConsentContext must be used within a CookieConsentProvider');
    }
    return context;
}

The CookieConsentProvider wraps your application and provides cookie consent state and actions to all child components. This allows you to access consent information and manage preferences from anywhere in your application, such as opening the consent modal via a "Manage Cookie Preferences" button.

Category-based consent

Fastack supports granular consent categories:

  • Necessary - Always enabled, required for site functionality
  • Functional - Enhanced functionality cookies
  • Analytics - Analytics and tracking cookies
  • Performance - Performance monitoring cookies
  • Advertisement - Advertising and marketing cookies

Server-side consent checking

Fastack provides server-side utilities to check consent before initializing analytics or other tracking services:

packages/cookies/utils/server.ts
import { ConsentCookies } from '@saas/cookies/utils';
import { cookies } from 'next/headers';

export async function GET() {
    const cookieStore = await cookies();
    
    if (!ConsentCookies.hasAnalyticsConsent(cookieStore)) {
        // Don't track analytics
        return Response.json({ tracked: false });
    }
    
    // Initialize analytics tracking
    return Response.json({ tracked: true });
}

The ConsentCookies.hasAnalyticsConsent() function checks server-side cookies to determine if the user has given analytics consent. This ensures that analytics and tracking services are only initialized when the user has given explicit consent, providing reliable GDPR compliance even before client-side JavaScript loads.

Consent management UI

Fastack includes pre-built UI components for cookie consent:

  • Cookie banner - Initial consent request
  • Consent modal - Detailed preference management
  • Preference persistence - Stores consent in cookies
  • Consent events - Dispatches events for analytics integration
packages/cookies/ui/components/cookie-consent.tsx
'use client';

import { CookieConsentBanner } from './cookie-consent-banner';
import { CookieConsentModal } from './cookie-consent-modal';
import { useCookieConsentContext } from '../context/cookie-consent-context';
import type { CookieConsentLabels } from '../types/labels';

interface CookieConsentProps {
    labels: CookieConsentLabels;
}

export function CookieConsent({ labels }: CookieConsentProps) {
    const {
        isOpen,
        isCustomizeOpen,
        preferences,
        acceptAll,
        rejectAll,
        savePreferences,
        openCustomize,
        closeCustomize,
    } = useCookieConsentContext();

    return (
        <>
            {isOpen && (
                <CookieConsentBanner
                    labels={labels}
                    onAcceptAll={acceptAll}
                    onRejectAll={rejectAll}
                    onCustomize={openCustomize}
                />
            )}
            <CookieConsentModal
                isOpen={isCustomizeOpen}
                onClose={closeCustomize}
                onSave={savePreferences}
                currentPreferences={preferences}
                labels={labels}
            />
        </>
    );
}

The CookieConsent component uses the useCookieConsentContext hook to access consent state and actions. It conditionally renders the banner when consent hasn't been given, and manages the modal for detailed preference customization.

packages/cookies/ui/components/cookie-consent-banner.tsx
'use client';

import { Button } from '@saas/ui-core';
import type { CookieConsentLabels } from '../types/labels';

interface CookieConsentBannerProps {
    labels: CookieConsentLabels;
    onAcceptAll: () => void;
    onRejectAll: () => void;
    onCustomize: () => void;
}

export function CookieConsentBanner({
    labels,
    onAcceptAll,
    onRejectAll,
    onCustomize,
}: CookieConsentBannerProps) {
    return (
        <div className="fixed bottom-0 left-0 right-0 z-50 p-4 md:p-6 bg-default-50 border-t border-divider shadow-lg">
            <div className="max-w-7xl mx-auto">
                <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
                    <div className="flex-1">
                        <h3 className="text-lg font-semibold mb-2">{labels.banner.title}</h3>
                        <p className="text-sm text-foreground/70 mb-4 md:mb-0">
                            {labels.banner.description}
                        </p>
                    </div>
                    <div className="flex flex-col sm:flex-row gap-3 md:ml-6">
                        <Button
                            variant="bordered"
                            size="sm"
                            onPress={onCustomize}
                            className="min-w-[120px]"
                        >
                            {labels.banner.customize}
                        </Button>
                        <Button
                            variant="bordered"
                            size="sm"
                            onPress={onRejectAll}
                            className="min-w-[120px]"
                        >
                            {labels.banner.rejectAll}
                        </Button>
                        <Button
                            color="primary"
                            size="sm"
                            onPress={onAcceptAll}
                            className="min-w-[120px]"
                        >
                            {labels.banner.acceptAll}
                        </Button>
                    </div>
                </div>
            </div>
        </div>
    );
}

The CookieConsentBanner component displays a fixed banner at the bottom of the page with three action buttons: Customize (opens the modal), Reject All, and Accept All. The banner is responsive and uses HeroUI components for consistent styling.

packages/cookies/ui/components/cookie-consent-modal.tsx
'use client';

import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, Switch } from '@saas/ui-core';
import { useState, useEffect } from 'react';
import type { CookieConsentPreferences } from '../../utils';
import type { CookieConsentLabels } from '../types/labels';

interface CookieConsentModalProps {
    isOpen: boolean;
    onClose: () => void;
    onSave: (preferences: Partial<CookieConsentPreferences>) => void;
    currentPreferences: CookieConsentPreferences | null;
    labels: CookieConsentLabels;
}

type CookieCategory = 'functional' | 'analytics' | 'performance' | 'advertisement';

export function CookieConsentModal({
    isOpen,
    onClose,
    onSave,
    currentPreferences,
    labels,
}: CookieConsentModalProps) {
    const [preferences, setPreferences] = useState<Partial<CookieConsentPreferences>>({
        functional: currentPreferences?.functional ?? false,
        analytics: currentPreferences?.analytics ?? false,
        performance: currentPreferences?.performance ?? false,
        advertisement: currentPreferences?.advertisement ?? false,
    });

    const handleToggle = (category: CookieCategory) => {
        setPreferences((prev) => ({
            ...prev,
            [category]: !prev[category],
        }));
    };

    const handleSave = () => {
        onSave(preferences);
    };

    return (
        <Modal isOpen={isOpen} onClose={onClose} size="2xl" placement="center">
            <ModalContent>
                <ModalHeader>
                    <h2 className="text-2xl font-bold">{labels.modal.title}</h2>
                </ModalHeader>
                <ModalBody>
                    <p className="text-foreground/70">{labels.modal.description}</p>

                    {/* Necessary Cookies - Always Enabled */}
                    <div className="p-4 rounded-lg border border-divider bg-content1">
                        <div className="flex items-center justify-between">
                            <div className="flex-1 mr-6">
                                <h3 className="font-semibold mb-1">{labels.categories.necessary.title}</h3>
                                <p className="text-sm text-foreground/70">{labels.categories.necessary.description}</p>
                            </div>
                            <Switch isSelected={true} isDisabled size="sm" />
                        </div>
                    </div>

                    {/* Other Categories */}
                    {['functional', 'analytics', 'performance', 'advertisement'].map((category) => (
                        <div key={category} className="p-4 rounded-lg border border-divider">
                            <div className="flex items-center justify-between">
                                <div className="flex-1">
                                    <h3 className="font-semibold mb-1">{labels.categories[category].title}</h3>
                                    <p className="text-sm text-foreground/70">{labels.categories[category].description}</p>
                                </div>
                                <Switch
                                    isSelected={preferences[category as CookieCategory] ?? false}
                                    onValueChange={() => handleToggle(category as CookieCategory)}
                                    size="sm"
                                />
                            </div>
                        </div>
                    ))}
                </ModalBody>
                <ModalFooter>
                    <Button variant="bordered" onPress={onClose}>
                        {labels.modal.cancel}
                    </Button>
                    <Button color="primary" onPress={handleSave}>
                        {labels.modal.save}
                    </Button>
                </ModalFooter>
            </ModalContent>
        </Modal>
    );
}

The CookieConsentModal component provides a detailed interface for managing cookie preferences. It displays all cookie categories with toggle switches, with necessary cookies always enabled and disabled. Users can customize their preferences for functional, analytics, performance, and advertisement cookies before saving.

Content Security Policy (CSP) headers

Fastack includes built-in Content Security Policy (CSP) headers to protect against XSS attacks and data injection. The CSP is automatically configured based on your analytics and monitoring setup (Google Tag Manager, Google Analytics, and PostHog):

packages/utils/security-headers.ts
/**
 * Generate Content Security Policy header
 */
export function generateCSPHeader(options?: {
    allowGTM?: boolean;
    allowGA?: boolean;
    allowPostHog?: boolean;
    postHogApiHost?: string;
    postHogUiHost?: string;
    allowGoogleFonts?: boolean;
    allowInlineScripts?: boolean;
    allowInlineStyles?: boolean;
    allowUnsafeEval?: boolean;
    additionalScriptSrc?: string[];
    additionalStyleSrc?: string[];
    additionalImgSrc?: string[];
    additionalConnectSrc?: string[];
}): string {
    const {
        allowGTM = true,
        allowGA = true,
        allowPostHog = !!process.env.NEXT_PUBLIC_POSTHOG_KEY,
        postHogApiHost = process.env.NEXT_PUBLIC_POSTHOG_API_HOST || 'https://us.i.posthog.com',
        postHogUiHost = process.env.NEXT_PUBLIC_POSTHOG_UI_HOST,
        allowGoogleFonts = true,
        allowInlineScripts = true,
        allowInlineStyles = true,
        allowUnsafeEval = process.env.NODE_ENV === 'development',
        additionalScriptSrc = [],
        additionalStyleSrc = [],
        additionalImgSrc = [],
        additionalConnectSrc = [],
    } = options || {};

    // Build CSP directives with appropriate sources
    const scriptSrc = [
        "'self'",
        ...(allowInlineScripts ? ["'unsafe-inline'"] : []),
        ...(allowUnsafeEval ? ["'unsafe-eval'"] : []),
        ...(allowGTM || allowGA ? ['https://www.googletagmanager.com'] : []),
        ...(allowGA ? ['https://www.google-analytics.com'] : []),
        ...(allowPostHog ? ['https://*.i.posthog.com'] : []),
        ...additionalScriptSrc,
    ];

    const styleSrc = [
        "'self'",
        ...(allowInlineStyles ? ["'unsafe-inline'"] : []),
        ...(allowGoogleFonts ? ['https://fonts.googleapis.com'] : []),
        ...additionalStyleSrc,
    ];

    // ... additional directives for fonts, images, connect, etc.

    const directives = [
        `default-src 'self'`,
        `script-src ${scriptSrc.join(' ')}`,
        `style-src ${styleSrc.join(' ')}`,
        `img-src ${imgSrc.join(' ')}`,
        `font-src ${fontSrc.join(' ')}`,
        `connect-src ${connectSrc.join(' ')}`,
        `frame-src ${frameSrc.join(' ')}`,
        `worker-src 'self' blob:`,
        `object-src 'none'`,
        `base-uri 'self'`,
        `form-action 'self'`,
        `frame-ancestors 'self'`,
        `upgrade-insecure-requests`,
    ];

    return directives.join('; ');
}

The generateCSPHeader function creates a comprehensive CSP policy that allows necessary resources while blocking potentially dangerous content. It automatically includes Google Tag Manager, Google Analytics, and PostHog sources if configured, and supports custom sources for additional services. In development mode, it also allows 'unsafe-eval' for Next.js Hot Module Replacement (HMR).

apps/fastack-boilerplate/src/middleware.ts
import { generateSecurityHeaders } from '@saas/utils';

export default async function middleware(req: NextRequest) {
    // ... authentication and routing logic ...

    // Set security headers including Content Security Policy
    const securityHeaders = generateSecurityHeaders({
        allowGTM: !!process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID,
        allowGA: !!process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID,
        allowPostHog: !!process.env.NEXT_PUBLIC_POSTHOG_KEY,
        postHogApiHost: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
        postHogUiHost: process.env.NEXT_PUBLIC_POSTHOG_UI_HOST,
        allowGoogleFonts: true,
        allowInlineScripts: true,
        allowInlineStyles: true,
    });

    Object.entries(securityHeaders).forEach(([key, value]) => {
        response.headers.set(key, value);
    });

    return response;
}

The middleware automatically sets CSP headers on all responses, configured based on your environment variables. This ensures that:

  • XSS protection - Prevents cross-site scripting attacks
  • Data injection prevention - Blocks unauthorized data sources
  • Analytics integration - Automatically allows GTM, GA, and PostHog if configured
  • Service worker support - Enables PWA functionality
  • HTTPS enforcement - Upgrades insecure requests automatically

Fastack also sets additional security headers including X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, and Permissions-Policy for comprehensive security protection.

Time saved: 20+ hours

Building GDPR-compliant cookie consent from scratch typically requires:

  • Implementing consent management system (4-6 hours)
  • Creating category-based consent logic (3-4 hours)
  • Building consent UI components (4-5 hours)
  • Implementing server-side consent checking (2-3 hours)
  • Integrating with analytics providers (2-3 hours)
  • Configuring Content Security Policy headers (2-3 hours)
  • Testing GDPR compliance (2-3 hours)
  • Documenting consent system (1-2 hours)

Total: 20-29 hours of development time that you save with Fastack.

Best practices included

Fastack follows GDPR best practices:

  • Privacy-first design - No tracking without explicit consent
  • Category-based consent - Granular user control
  • Server-side enforcement - Reliable consent checking
  • Consent persistence - Remembers user preferences
  • Analytics integration - Respects consent preferences
  • Content Security Policy - Built-in CSP headers for XSS protection

Conclusion

GDPR compliance is essential for SaaS applications, and cookie consent management is a critical component. Fastack provides a complete, privacy-first cookie consent system that saves you weeks of development time while ensuring legal compliance and user trust.

With Fastack, you get:

  • Cookie consent provider with React context for global state management
  • Category-based consent for granular user control
  • Server-side consent checking for reliable enforcement
  • Pre-built UI components for consent management
  • Analytics integration that respects user preferences
  • Content Security Policy headers for XSS and data injection protection
  • GDPR-compliant consent system built-in from day one

Ready to build your SaaS faster?

Get our scalable, production-ready boilerplate to save endless hours of development and setup

GDPR-compliant SaaS: cookie consent and data protection in Next.js - Fastack