Modern Digital Magazine Platform for Sezai Karakoç Anadolu Lisesi
"Şu içimizin de balkonu olsaydı, çıkıp arada nefes alsaydık."
Features • Architecture • Tech Stack • Getting Started • Deployment • Analytics
Interactive Flipbook Experience
Balkon Dergisi is a production-grade digital magazine platform that transforms PDF publications into immersive, interactive reading experiences. Built for Sezai Karakoç Anadolu Lisesi, it combines enterprise-level engineering with student publication accessibility.
The name "Balkon" (Turkish for "Balcony") embodies the platform's philosophy—providing students a space to breathe, express themselves, and share their creative work with the world.
| Capability | Description |
|---|---|
| 📚 Interactive Flipbook Reader | Hardware-accelerated page-turning with zoom, keyboard navigation, and touch gestures |
| 📄 PDF-to-Magazine Pipeline | Automated PDF processing with page extraction, WebP conversion, and parallel uploads |
| 🔐 Admin Dashboard | Complete CRUD operations with optimistic locking, transaction rollback, and audit trails |
| 📊 Analytics Engine | Session-based reader analytics with device tracking and engagement metrics |
| 🛡️ Production Security | CSP headers, rate limiting, input sanitization, and Sentry error monitoring |
Built on FlipbookViewer, the reading interface delivers magazine-quality interactions:
- Page Flip Animation powered by react-pageflip with smooth 60fps transitions
- Zoom Controls (1x → 4x scale) via
ZoomContainerwith pinch-to-zoom support - Page Lock Feature prevents accidental navigation during zoomed reading
- Keyboard Navigation (Arrow keys, Space, Escape) with accessibility aria-live announcements
- Intelligent Preloading — Adjacent pages prefetch for instant flips
- Responsive Dimensions using
useResponsiveDimensionsfor dynamic viewport scaling
Enterprise-grade PDF processing with comprehensive error recovery:
PDF File → Page Extraction → WebP Conversion → Parallel Upload → Database Record
↓ ↓ ↓ ↓ ↓
Validation pdf.js Canvas API Batch (3 concurrent) Transaction
Magic bytes Worker quality=0.85 Retry (3 attempts) Rollback
Key Technical Details:
- PDF parsing via pdf.js with dedicated web worker
- Page rendering to OffscreenCanvas with configurable target height (2000px default)
- WebP encoding at 85% quality for optimal size/quality balance
- Batched parallel uploads (3 concurrent) with exponential backoff retry
- Wake Lock API integration to prevent device sleep during long uploads
- Full transaction support: storage uploads roll back if database insert fails
Real-time analytics powered by Supabase with AnalyticsService:
- Session Tracking — Device type, user agent, start time, duration
- Event Buffering — Batched writes (5 events or 10s interval) for performance
- Graceful Degradation — Fallback IDs when database is unavailable
- Analytics Dashboard at
/admin/analyticswith:- 📈 Total sessions, unique users, average duration metrics
- 📱 Device distribution (mobile/tablet/desktop) pie charts
- 📆 Daily session trends via Recharts
Secure admin access via Supabase Auth with SSR-compatible session management:
- Middleware Protection (
middleware.ts) — Route guards for/adminpaths - Auto-Refresh Sessions — Proactive token renewal within 5 minutes of expiry
- Role-Based Access — Admin-only upload and magazine management
- Error Recovery — Graceful fallback redirects on auth failures
Full-featured CMS at /admin with:
| Feature | Implementation |
|---|---|
| Magazine Upload | Drag-and-drop PDF with real-time processing progress |
| Magazine CRUD | Create, rename, delete with optimistic locking |
| Version Conflicts | Concurrency detection with user-friendly resolution UI |
| Audit Logging | Structured logging with operation context via custom Logger |
The application follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Hero │ │ MagazineCard│ │ FlipbookViewer │ │
│ │ Component │ │ Grid │ │ (react-pageflip) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Hooks Layer │
│ ┌───────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │usePDFProcessor│ │ useFileUpload│ │useMagazineAnalytics│ │
│ └───────────────┘ └──────────────┘ └────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Services Layer │
│ ┌───────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ MagazineService │ │ UploadService │ │ AnalyticsService │
│ │ (Transactions) │ │ (Batch Upload) │ │ (Event Queue)│ │
│ └───────────────────┘ └─────────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Supabase │ │ Logger │ │ TransactionManager │ │
│ │ (Storage) │ │ (Sentry) │ │ (Rollback Support) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
src/
├── app/ # Next.js App Router
│ ├── (admin)/ # Admin route group (protected)
│ │ └── admin/
│ │ ├── login/ # Authentication flow
│ │ ├── analytics/ # Reader analytics dashboard
│ │ └── [CRUD components] # Upload, Table, Actions
│ ├── (public)/ # Public route group
│ │ ├── page.tsx # Landing page
│ │ └── dergi/[issueNumber]/ # Magazine reader pages
│ ├── layout.tsx # Root layout with SEO & JSON-LD
│ ├── sitemap.ts # Dynamic sitemap generation
│ └── robots.ts # Robots.txt configuration
│
├── components/
│ ├── FlipbookViewer.tsx # Core reader with zoom & analytics
│ ├── Hero.tsx # Animated landing section
│ ├── MagazineCard.tsx # Issue cards with hover effects
│ └── ui/ # Radix-based design system
│
├── hooks/
│ ├── usePDFProcessor.ts # PDF.js integration
│ ├── useFileUpload.ts # Multi-file upload orchestration
│ ├── useMagazineAnalytics.ts # Reader session tracking
│ └── useWakeLock.ts # Prevent sleep during uploads
│
├── lib/
│ ├── services/
│ │ ├── MagazineService.ts # Transactions, optimistic locking
│ │ ├── UploadService.ts # Batch uploads with retry
│ │ ├── AnalyticsService.ts # Event queue with batching
│ │ └── storage/ # Storage abstraction layer
│ ├── validators/ # Zod schemas for all inputs
│ ├── errors/ # Typed error hierarchy
│ └── guards/ # Runtime type guards
│
└── types/ # TypeScript definitions
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 15.5.7 | App Router, Server Components, ISR, Server Actions |
| React | 19.1.2 | UI library with latest concurrent features |
| TypeScript | 5.x | End-to-end type safety with strict mode |
| Tailwind CSS | 4.x | Utility-first with custom PostCSS pipeline |
| Technology | Version | Purpose |
|---|---|---|
| Supabase | Latest | PostgreSQL database, object storage, authentication |
| Supabase SSR | Latest | Server-side session management |
| Library | Version | Purpose |
|---|---|---|
| pdf.js | 4.x | PDF parsing and canvas-based rendering with worker support |
| react-pageflip | 2.x | Flipbook animations with CSS 3D transforms |
| Recharts | 2.x | Composable React charts for analytics |
| Zod | 3.x | Runtime schema validation for forms and API inputs |
| Motion | Latest | Declarative animations for UI interactions |
| Radix UI | Latest | Accessible component primitives (Dialog, Dropdown, Select) |
| Sentry | Latest | Error tracking and performance monitoring |
| React Hook Form | 7.x | Performant forms with validation |
The usePDFProcessor hook encapsulates the entire transformation workflow:
const { processPDF, processedPages, totalPages, isProcessing, error } = usePDFProcessor()
const result = await processPDF(file, {
targetHeight: 2000,
quality: 0.85,
onProgress: (current, total) => console.log(`Processing ${current}/${total}`)
})
// Returns: { pages: Blob[], totalPages: number }The useMagazineAnalytics hook automatically tracks:
- Session initialization on magazine view
- Tab visibility changes (pause/resume events)
- Reader interactions (page turns tracked via event metadata)
The application expects the following Supabase tables:
-- Magazines table
CREATE TABLE magazines (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT NOT NULL,
issue_number INTEGER UNIQUE NOT NULL,
publication_date DATE NOT NULL,
cover_url TEXT NOT NULL,
page_count INTEGER NOT NULL,
version INTEGER DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Analytics sessions
CREATE TABLE analytics_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
magazine_id UUID REFERENCES magazines(id),
device_type TEXT NOT NULL,
user_agent TEXT,
started_at TIMESTAMPTZ DEFAULT NOW(),
last_active_at TIMESTAMPTZ
);
-- Analytics events
CREATE TABLE analytics_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID REFERENCES analytics_sessions(id),
event_type TEXT NOT NULL,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);Storage Bucket Structure:
magazines/
├── {issue_number}/
│ ├── cover.webp
│ └── pages/
│ ├── 1.webp
│ ├── 2.webp
│ └── ...
Production-hardened security configuration:
// Content Security Policy (from next.config.ts)
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net",
"img-src 'self' data: blob: https:",
"frame-ancestors 'none'",| Header | Value |
|---|---|
Content-Security-Policy |
Strict CSP with Supabase domain allowlist |
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
X-XSS-Protection |
1; mode=block |
Referrer-Policy |
strict-origin-when-cross-origin |
| Stage | Recovery Strategy |
|---|---|
| PDF Processing | Retry with reduced quality; skip corrupt pages |
| Page Upload | Exponential backoff (3 attempts); partial upload state persisted |
| Cover Upload | Falls back to first PDF page if custom cover fails |
| Database Insert | Full rollback of uploaded files |
sentry.client.config.tsfor frontend errorssentry.server.config.tsfor API routessentry.edge.config.tsfor middleware errors
- Node.js ≥ 18.0.0
- npm ≥ 8.0.0
- Supabase Account — Create free project
# Clone the repository
git clone https://github.com/Woffluon/Balkon-Magazine.git
cd Balkon-Magazine
# Install dependencies
npm install
# Configure environment variables
cp .env.example .env.localUpdate .env.local with your credentials:
# Required
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Optional
SENTRY_DSN=your-sentry-dsn
NEXT_PUBLIC_SENTRY_DSN=your-client-sentry-dsn- Create database tables using the SQL schema above
- Configure Storage bucket:
- Bucket name:
magazines - Public access: Enabled
- File size limit: 500MB
- Bucket name:
# Start development server with Turbopack
npm run dev| Command | Description |
|---|---|
npm run dev |
Start dev server with Turbopack |
npm run build |
Create production build |
npm start |
Start production server |
npm run lint |
Run ESLint |
npm run type-check |
TypeScript type checking |
npm run clean |
Clean build artifacts |
This project is optimized for Vercel deployment:
- Connect repository to Vercel dashboard
- Set environment variables in Vercel project settings
- Deploy — Vercel auto-detects Next.js configuration
Production Configuration:
- Output mode:
standalone - Server Actions body size limit: 500MB (for large PDF uploads)
- Image Optimization: Disabled (
unoptimized: true) for static hosting compatibility - Compression: Enabled via
compress: true
npm run build
npm startEnsure your hosting environment supports:
- Node.js 18+
- Environment variable injection
- Persistent storage for
.nextcache (optional)
The postinstall script automatically copies pdf.worker.min.mjs to public/:
node -e "require('fs').copyFileSync('node_modules/pdfjs-dist/build/pdf.worker.min.mjs', 'public/pdf.worker.min.mjs')"Verify the worker is accessible at /pdf.worker.min.mjs after deployment.
Access magazine insights at /admin/analytics:
- Overview Cards — Total sessions, unique users, average duration
- Device Distribution — Pie chart breakdown (mobile/tablet/desktop)
- Engagement Trends — Daily session counts with date range filters
React Component → useMagazineAnalytics → AnalyticsService → Supabase Tables
The analytics system uses:
- Batched Writes — Events buffer to 5 items or 10s, whichever comes first
- Retry Logic — Failed flushes re-queue (max 100 pending)
- Graceful Degradation — Fallback UUIDs if database unavailable
The platform is equipped with enterprise-grade performance optimizations to deliver the best experience on mobile networks (3G/2G) and low-end devices:
-
Bundle Analysis & Code Splitting:
- JS bundle composition is optimized using
@next/bundle-analyzer. - Webpack
splitChunksconfiguration dynamically separates large libraries (pdfjs-dist,recharts,react-pageflip) into dedicated chunks. - Non-critical pages and components (e.g., Admin Analytics) are lazily loaded via dynamic imports (
next/dynamic).
- JS bundle composition is optimized using
-
Connection-Aware OptimizedImage:
- Network Information API integration scales image quality to 60% on slow networks (2G/3G) or when Data Saver is active, and 90% quality on fast connections.
- Gaussian blur SVG filters generate dynamic Base64 blur placeholders to prevent Layout Shifts (CLS).
- Priority loading (
priorityandfetchpriority="high") is active for LCP candidate images.
-
Offline Support & Caching Strategies (Service Worker):
- Static assets (CSS, JS, Fonts) use a browser-level Cache-First strategy.
- Magazine pages utilize a Stale-While-Revalidate caching mechanism. The image cache is constrained by a 50MB LRU (Least Recently Used) eviction policy.
- If connection is lost entirely, a custom-designed
/offline.htmlfallback page is presented to the user.
-
Runtime & Animation Optimizations:
- Custom utility hooks (
useDebounce,useThrottle, andusePassiveEventListenerfor touch/scroll events) are utilized to minimize main thread blocking. - Intensive components and computations are optimized using
React.memo,useMemo, anduseCallback. - Respects
prefers-reduced-motionmedia queries to disable CPU-intensive animations for users requesting reduced motion. - PDF rendering operations are throttled to a maximum concurrency of 3, and memory is reclaimed instantly via page cleanup to prevent browser memory leaks.
- Custom utility hooks (
-
Performance Monitoring (Web Vitals) & Budget Control:
- Track Core Web Vitals (LCP, FID, CLS, FCP, TTFB, INP) across all pages using
next/web-vitals. - Warns on budget overruns in development console, and logs metrics to
/api/analytics/vitalsusingnavigator.sendBeaconin production to be recorded in Supabase. - Performance dashboards are available at
/admin/performancewith visual charts to track metrics over time.
- Track Core Web Vitals (LCP, FID, CLS, FCP, TTFB, INP) across all pages using
Platform, mobil ağlarda (3G/2G) ve düşük donanımlı cihazlarda en iyi deneyimi sunmak için kurumsal düzeyde performans optimizasyonlarıyla donatılmıştır:
-
Paket Analizi ve Kod Bölme:
@next/bundle-analyzerentegrasyonu ile JavaScript paket bileşimi optimize edilmiştir.- Webpack
splitChunksyapılandırması sayesinde büyük kütüphaneler (pdfjs-dist,recharts,react-pageflip) ayrı JavaScript parçalarına bölünmüştür. - Kritik olmayan sayfalar ve bileşenler (örn. Yönetici İstatistikleri) dynamic imports (
next/dynamic) ile tembelce yüklenir.
-
Bağlantı Hızına Duyarlı OptimizedImage:
- Network Information API entegrasyonu ile 2G/3G veya veri tasarrufu (save-data) etkin ağlarda görseller %60 kalitede, hızlı ağlarda ise %90 kalitede sunulur.
- SVG Gauss Bulanıklığı filtreleri kullanılarak oluşturulan dinamik Base64 görsel yer tutucuları (blur placeholders) ile sayfa düzeni kayması (CLS) önlenir.
- LCP adayı görseller için öncelikli yükleme (
priorityvefetchpriority="high") etkindir.
-
Çevrimdışı Çalışma ve Önbellek Stratejileri (Service Worker):
- Statik varlıklar (CSS, JS, Yazı Tipleri) için tarayıcı düzeyinde Cache-First stratejisi uygulanır.
- Dergi sayfaları görselleri için Stale-While-Revalidate önbellek yöntemi kullanılır. Görüntü önbelleği 50MB LRU (en son kullanılan) tahliye politikasıyla sınırlandırılmıştır.
- Bağlantı tamamen koptuğunda kullanıcıya özel tasarlanmış
/offline.htmlhata sayfası sunulur.
-
Çalışma Zamanı ve Animasyon İyileştirmeleri:
useDebounce,useThrottleve dokunmatik/kaydırma olaylarının performansını artıranusePassiveEventListenerözel kancaları (hooks) yazılmıştır.- Pahalı bileşenler ve hesaplamalar için
React.memo,useMemoveuseCallbackoptimizasyonları uygulanmıştır. prefers-reduced-motionortam sorgusu (media query) algılanarak animasyonları devre dışı bırakma desteği eklenmiştir.- PDF oluşturma/çizim işlemlerinde eş zamanlı işlem limiti 3 olarak sınırlandırılmış ve bellek sızıntılarını önlemek için PDF.js nesneleri anında bellekten temizlenir.
-
Performans İzleme (Web Vitals) ve Bütçe Kontrolü:
- Tüm sayfalarda LCP, FID, CLS, FCP, TTFB, INP metrikleri
next/web-vitalsile izlenir. - Geliştirme ortamında bütçe aşımlarında konsola uyarı basılır. Üretim ortamında ise performans metrikleri
navigator.sendBeaconile/api/analytics/vitalsAPI'sine ve oradan Supabase veritabanına kaydedilir. - Yönetici panelindeki
/admin/performanceekranından bu metrikler görsel bütçe grafikeriyle izlenebilmektedir.
- Tüm sayfalarda LCP, FID, CLS, FCP, TTFB, INP metrikleri
Contributions are welcome! Please ensure:
- All code passes
npm run lintandnpm run type-check - TypeScript Strict Mode — All code must compile without errors
- Component Structure — Keep components under 300 lines
- Error Handling — Use structured error patterns from
ErrorHandler - Logging — Use
Loggerinstead ofconsole.log - New features include appropriate error handling and accessibility compliance
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License — see the LICENSE file for details.
Copyright © 2025 Efe Arabacı
