Building a Professional QR Code Platform
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:
- Generate QR codes for URLs
- Let users download them
- Track basic analytics
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:
- 50,000+ QR codes generated monthly
- 500,000+ scans tracked
- 99.8% uptime across all services
- <200ms average QR generation time
Next Generation Features
- Bulk QR Generation: CSV upload for mass QR creation
- Advanced Analytics: Heat maps and conversion tracking
- API Platform: Public API for developers
- Team Collaboration: Shared workspaces and permissions
- Smart Templates: AI-powered QR design suggestions
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.