Building a Professional QR Code Platform

ยท 15 min read ยท Live Project

When I started building SnapIT QR, I thought QR codes were simple - just encode some text and display a pattern. Six months later, I had built a comprehensive platform with custom branding, real-time analytics, URL shortening, and one of the most robust error handling systems I've ever implemented.

The Evolution Beyond Basic QR Codes

The initial requirements seemed straightforward:

But as I dug deeper into user needs, the complexity exploded:

๐ŸŽจ Custom Branding

Users wanted branded QR codes with custom colors, logos, and styles that matched their brand identity.

๐Ÿ“Š Real-time Analytics

Track scans by location, device, time, and referrer with detailed dashboards.

๐Ÿ”— URL Shortening

Integrated URL shortener for cleaner QR codes and better tracking.

๐Ÿ”„ Dynamic QR Codes

Change destination URLs without regenerating the QR code.

The Custom Branding Challenge

๐Ÿšจ Problem

QR codes have strict requirements for scanability, but users wanted complete visual customization. How do you balance brand aesthetics with technical functionality?

This led to one of the most complex algorithms in the project:

class BrandedQRGenerator {
    constructor() {
        this.contrastThreshold = 4.5; // WCAG AA compliance
        this.logoMaxSize = 0.3; // 30% of QR code area
        this.errorCorrection = 'H'; // High error correction for branded codes
    }

    async generateBrandedQR(data, brandingOptions) {
        const {
            primaryColor,
            backgroundColor,
            logoUrl,
            cornerStyle,
            pixelStyle
        } = brandingOptions;

        // Step 1: Validate color contrast
        const contrast = this.calculateContrast(primaryColor, backgroundColor);
        if (contrast < this.contrastThreshold) {
            throw new Error('Insufficient color contrast for reliable scanning');
        }

        // Step 2: Generate base QR with high error correction
        const qr = qrcode.create(data, {
            errorCorrectionLevel: this.errorCorrection,
            margin: 2,
            color: {
                dark: primaryColor,
                light: backgroundColor
            }
        });

        // Step 3: Apply custom styling
        const canvas = await this.applyCustomStyling(qr, brandingOptions);

        // Step 4: Add logo with safe positioning
        if (logoUrl) {
            await this.addLogoSafely(canvas, logoUrl);
        }

        // Step 5: Validate scanability
        const isValid = await this.validateScanability(canvas);
        if (!isValid) {
            console.warn('Generated QR may have scanning issues');
        }

        return canvas;
    }

    calculateContrast(color1, color2) {
        const rgb1 = this.hexToRgb(color1);
        const rgb2 = this.hexToRgb(color2);

        const l1 = this.relativeLuminance(rgb1);
        const l2 = this.relativeLuminance(rgb2);

        const bright = Math.max(l1, l2);
        const dark = Math.min(l1, l2);

        return (bright + 0.05) / (dark + 0.05);
    }

    async addLogoSafely(canvas, logoUrl) {
        const ctx = canvas.getContext('2d');
        const logo = await this.loadImage(logoUrl);

        // Calculate safe logo size and position
        const qrSize = canvas.width;
        const logoSize = Math.min(qrSize * this.logoMaxSize, logo.width, logo.height);

        // Position in center, avoiding critical areas
        const x = (qrSize - logoSize) / 2;
        const y = (qrSize - logoSize) / 2;

        // Add white background for logo visibility
        ctx.fillStyle = 'white';
        ctx.fillRect(x - 5, y - 5, logoSize + 10, logoSize + 10);

        // Draw logo
        ctx.drawImage(logo, x, y, logoSize, logoSize);
    }
}

The Ultra-Aggressive Error Handling System

One of the most unique aspects of SnapIT QR is its error handling philosophy. After encountering numerous edge cases with different devices and browsers, I implemented what I call "ultra-aggressive error suppression."

โœ… Innovation

Instead of letting any error break the user experience, the system catches and gracefully handles every possible failure point.

class ErrorSuppressionSystem {
    constructor() {
        this.setupGlobalErrorHandling();
        this.interceptConsoleErrors();
        this.setupFetchInterception();
    }

    setupGlobalErrorHandling() {
        // Catch all unhandled errors
        window.addEventListener('error', (event) => {
            this.logError('Global Error', event.error);
            event.preventDefault(); // Prevent default browser error handling
        });

        // Catch all unhandled promise rejections
        window.addEventListener('unhandledrejection', (event) => {
            this.logError('Unhandled Promise Rejection', event.reason);
            event.preventDefault();
        });

        // Catch all resource loading errors
        window.addEventListener('error', (event) => {
            if (event.target !== window) {
                this.handleResourceError(event.target);
                event.preventDefault();
            }
        }, true);
    }

    interceptConsoleErrors() {
        const originalError = console.error;
        console.error = (...args) => {
            this.logError('Console Error', args);
            // Optionally call original for debugging
            if (this.debugMode) {
                originalError.apply(console, args);
            }
        };
    }

    setupFetchInterception() {
        const originalFetch = window.fetch;
        window.fetch = async (...args) => {
            try {
                const response = await originalFetch.apply(window, args);
                if (!response.ok) {
                    this.logError('Fetch Error', {
                        url: args[0],
                        status: response.status,
                        statusText: response.statusText
                    });
                }
                return response;
            } catch (error) {
                this.logError('Fetch Exception', error);
                // Return a mock response to prevent crashes
                return new Response(JSON.stringify({ error: 'Network error' }), {
                    status: 500,
                    headers: { 'Content-Type': 'application/json' }
                });
            }
        };
    }

    logError(category, error) {
        // Send to analytics service
        this.sendErrorAnalytics(category, error);

        // Store locally for debugging
        this.storeErrorLocally(category, error);
    }
}

Real-time Analytics Architecture

The analytics system needed to track QR code scans across different devices and platforms while maintaining user privacy.

๐Ÿšจ Problem

QR codes are scanned by diverse devices with different capabilities. How do you track analytics without JavaScript running on the target device?

The solution involved a sophisticated redirect system:

class QRAnalyticsTracker {
    constructor() {
        this.trackingDomain = 'qr.snapitqr.com';
        this.geoIpService = 'https://api.snapitqr.com/geoip';
    }

    generateTrackableURL(originalUrl, qrId) {
        const trackingUrl = new URL(`https://${this.trackingDomain}/t/${qrId}`);
        trackingUrl.searchParams.set('dest', encodeURIComponent(originalUrl));
        trackingUrl.searchParams.set('ts', Date.now());

        return trackingUrl.toString();
    }

    async handleScan(request) {
        const { qrId, dest, userAgent, ip, referer } = this.extractScanData(request);

        // Collect analytics data
        const analyticsData = {
            qrId,
            timestamp: new Date().toISOString(),
            userAgent: userAgent,
            deviceInfo: this.parseDeviceInfo(userAgent),
            location: await this.getLocationFromIP(ip),
            referer: referer,
            scanId: this.generateScanId()
        };

        // Store analytics (non-blocking)
        this.storeAnalytics(analyticsData).catch(err =>
            console.error('Analytics storage failed:', err)
        );

        // Redirect to destination
        return new Response(null, {
            status: 302,
            headers: {
                'Location': dest,
                'Cache-Control': 'no-cache, no-store, must-revalidate',
                'Expires': '0'
            }
        });
    }

    parseDeviceInfo(userAgent) {
        // Comprehensive device detection
        const isAndroid = /Android/i.test(userAgent);
        const isIOS = /iPhone|iPad|iPod/i.test(userAgent);
        const isDesktop = !isAndroid && !isIOS && !/Mobile/i.test(userAgent);

        return {
            platform: isAndroid ? 'Android' : isIOS ? 'iOS' : isDesktop ? 'Desktop' : 'Unknown',
            isMobile: isAndroid || isIOS,
            browser: this.detectBrowser(userAgent),
            os: this.detectOS(userAgent)
        };
    }
}

The Dynamic QR Code System

Static QR codes are limited - once printed, you can't change the destination. Dynamic QR codes solve this by always pointing to a redirect service.

class DynamicQRManager {
    constructor() {
        this.redirectService = 'https://api.snapitqr.com/redirect';
    }

    async createDynamicQR(userConfig) {
        // Generate unique QR ID
        const qrId = this.generateUniqueId();

        // Store configuration
        await this.storeQRConfig(qrId, {
            originalUrl: userConfig.url,
            title: userConfig.title,
            description: userConfig.description,
            isActive: true,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            owner: userConfig.userId,
            settings: {
                passwordProtected: userConfig.passwordProtected || false,
                expiresAt: userConfig.expiresAt || null,
                scanLimit: userConfig.scanLimit || null
            }
        });

        // Generate QR code pointing to redirect service
        const redirectUrl = `${this.redirectService}/${qrId}`;
        const qrCodeData = await this.generateQRCode(redirectUrl, userConfig.branding);

        return {
            qrId,
            qrCodeData,
            redirectUrl,
            managementUrl: `https://snapitqr.com/manage/${qrId}`
        };
    }

    async updateQRDestination(qrId, newUrl, userId) {
        // Verify ownership
        const config = await this.getQRConfig(qrId);
        if (config.owner !== userId) {
            throw new Error('Unauthorized: Cannot modify QR code');
        }

        // Update configuration
        await this.updateQRConfig(qrId, {
            originalUrl: newUrl,
            updatedAt: new Date().toISOString()
        });

        // QR code image remains the same, but scans go to new URL
        return { success: true, newUrl };
    }
}

Performance Optimization Strategies

With complex QR generation and real-time analytics, performance became critical:

1. Client-side QR Generation

Most QR codes are generated entirely in the browser to reduce server load:

// Web Workers for heavy QR generation
class QRWorkerManager {
    constructor() {
        this.workers = [];
        this.maxWorkers = navigator.hardwareConcurrency || 4;
        this.initializeWorkers();
    }

    async generateQR(data, options) {
        const worker = this.getAvailableWorker();

        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                reject(new Error('QR generation timeout'));
            }, 10000);

            worker.postMessage({ data, options });

            worker.onmessage = (event) => {
                clearTimeout(timeout);
                if (event.data.error) {
                    reject(new Error(event.data.error));
                } else {
                    resolve(event.data.qrCode);
                }
            };
        });
    }
}

2. Progressive Image Loading

QR codes with custom branding can be large. Progressive loading improves perceived performance:

class ProgressiveQRLoader {
    async loadQRWithPlaceholder(container, qrData) {
        // Show low-quality placeholder immediately
        const placeholder = await this.generatePlaceholder(qrData);
        container.innerHTML = placeholder;

        // Generate high-quality version in background
        const fullQR = await this.generateFullQuality(qrData);

        // Smooth transition to full quality
        this.crossfadeImages(container, fullQR);
    }

    generatePlaceholder(qrData) {
        // Generate simple black/white QR without branding
        return qrcode.toSVG(qrData.content, {
            width: 200,
            margin: 1,
            color: { dark: '#000', light: '#fff' }
        });
    }
}

Lessons from Production

The iOS Safari Quirk

iOS Safari has unique QR scanning behavior that broke analytics for months:

๐Ÿšจ Problem

iOS Safari's built-in QR scanner doesn't send referrer headers, making device detection impossible.

Solution required fingerprinting the request pattern:

function detectIOSSafariQR(request) {
    const userAgent = request.headers.get('User-Agent');
    const hasReferer = request.headers.has('Referer');

    // iOS Safari QR scanner pattern
    return userAgent.includes('Safari') &&
           userAgent.includes('iPhone') &&
           !hasReferer &&
           request.headers.get('Sec-Fetch-Site') === 'none';
}

The WhatsApp Integration Discovery

Users were sharing QR codes in WhatsApp, but the app's preview generation was triggering false analytics. This led to implementing bot detection:

const botPatterns = [
    /WhatsApp/i,
    /facebookexternalhit/i,
    /Twitterbot/i,
    /LinkedInBot/i,
    /SkypeUriPreview/i
];

function isBotRequest(userAgent) {
    return botPatterns.some(pattern => pattern.test(userAgent));
}

Current Metrics & Future Plans

SnapIT QR now processes:

Next Generation Features

Technical Architecture Summary

Frontend:
โ”œโ”€โ”€ Vanilla JavaScript (ES6+)
โ”œโ”€โ”€ Custom CSS with CSS Variables
โ”œโ”€โ”€ Web Workers for QR generation
โ”œโ”€โ”€ Service Worker for offline capability
โ”œโ”€โ”€ IndexedDB for local storage

Backend:
โ”œโ”€โ”€ AWS Lambda (Node.js)
โ”œโ”€โ”€ DynamoDB for QR metadata
โ”œโ”€โ”€ S3 for QR image storage
โ”œโ”€โ”€ CloudFront for global distribution
โ”œโ”€โ”€ API Gateway for REST endpoints

Analytics:
โ”œโ”€โ”€ Custom tracking pixel system
โ”œโ”€โ”€ Real-time data processing
โ”œโ”€โ”€ GeoIP location detection
โ”œโ”€โ”€ Device fingerprinting
โ””โ”€โ”€ Privacy-compliant data retention

Building SnapIT QR taught me that even seemingly simple products hide complex technical challenges. The key is building systems that gracefully handle edge cases while maintaining a smooth user experience.