DEV Community

Sachin Maurya
Sachin Maurya

Posted on

The 10-Line Architecture That Scaled to 50+ Pages

"The best architecture is invisible to the developer using it" - This principle led us to create something that fundamentally changed how we build web applications.

Six months ago, our Next.js application at Kreate Technologies was drowning in technical debt. 50+ pages, 300+ components, and a team struggling to maintain consistency. Every new page took 2 hours to build. Every bug fix risked breaking three other features.

Today, creating a new page takes 18 minutes. Our bundle size dropped 65%. Performance scores hit 95+. Developer productivity increased 300%.

The secret? We treat pages as data structures, not code.

Table of Contents

The Problem: Component Soup {#the-problem}

Picture this typical Next.js page:

// homepage.tsx - The old way (200+ lines) import HeroSection from '@/components/sections/HeroSection'; import ServicesSection from '@/components/sections/ServicesSection'; import TestimonialsSection from '@/components/sections/TestimonialsSection'; import ClientsSection from '@/components/sections/ClientsSection'; import FAQSection from '@/components/sections/FAQSection'; import CTASection from '@/components/sections/CTASection'; // ... 20 more imports export default function HomePage() { const [heroData, setHeroData] = useState(null); const [servicesData, setServicesData] = useState(null); // ... 10 more state variables useEffect(() => { fetchHeroData().then(setHeroData); fetchServicesData().then(setServicesData); // ... 10 more API calls }, []); if (!heroData || !servicesData /* ... */) { return <PageLoader />; } return ( <> <HeroSection title={heroData.title} subtitle={heroData.subtitle} image={heroData.image} ctaText={heroData.ctaText} ctaLink={heroData.ctaLink} />  <ServicesSection services={servicesData} layout="grid" columns={3} />  {/* ... 15 more sections */} </>  ); } 
Enter fullscreen mode Exit fullscreen mode

Problems everywhere:

  • 🔴 Code duplication across 50+ pages
  • 🔴 No lazy loading (450KB initial bundle)
  • 🔴 Prop drilling nightmare
  • 🔴 Error cascading (one failure = page crash)
  • 🔴 Inconsistent patterns
  • 🔴 2+ weeks for new developer onboarding

The Breakthrough Moment {#the-breakthrough}

During a code review, I noticed something shocking: 80% of our pages were just different arrangements of the same 30 components.

What if we could define pages as configurations instead of code?

// The same page - revolutionary approach (10 lines) import { PageRenderer } from '@/components/common/PageRenderer'; import { HOMEPAGE_SECTIONS } from '@/config/sectionsConfig'; export default function HomePage() { return <PageRenderer sections={HOMEPAGE_SECTIONS} pagelink="home" />; } 
Enter fullscreen mode Exit fullscreen mode
// sectionsConfig.ts - The magic happens here export const HOMEPAGE_SECTIONS: Section[] = [ createSection('hero', () => import('./HeroSection'), { type: 'banner' }), createSection('services', () => import('./ServicesSection'), { type: 'grid', cols: 3 }), createSection('testimonials', () => import('./TestimonialsSection'), { type: 'carousel' }), createSection('clients', () => import('./ClientsSection'), { type: 'logos' }), createSection('faq', () => import('./FAQSection'), { type: 'accordion' }), ]; 
Enter fullscreen mode Exit fullscreen mode

This simple shift unleashed a cascade of innovations.

The Architecture Deep Dive {#the-architecture}

The Section Factory Pattern

interface Section { id: string; Component: React.LazyExoticComponent<React.ComponentType<any>>; skeleton: SkeletonConfig; defaultProps?: Record<string, unknown>; } interface SkeletonConfig { type: 'banner' | 'grid' | 'carousel' | 'list' | 'card'; cols?: number; rows?: number; animated?: boolean; } const createSection = ( id: string, importer: () => Promise<{ default: React.ComponentType<any> }>, skeleton: SkeletonConfig, defaultProps?: Record<string, unknown> ): Section => { return { id, Component: React.lazy(importer), skeleton, defaultProps }; }; 
Enter fullscreen mode Exit fullscreen mode

The Orchestrator: PageRenderer

The magic happens in our PageRenderer component:

export const PageRenderer: React.FC<PageRendererProps> = ({ sections, globalProps = {}, sectionProps = {}, pagelink }) => { const renderSection = useCallback((section: Section, index: number) => { const mergedProps = { ...globalProps, ...section.defaultProps, ...sectionProps[section.id], pagelink }; return ( <SectionErrorBoundary key={section.id} sectionName={section.id}> <LazySection Component={() => <section.Component {...mergedProps} />}  skeleton={section.skeleton} sectionName={section.id} />  </SectionErrorBoundary>  ); }, [globalProps, sectionProps, pagelink]); return ( <div className="page-container"> {sections.map(renderSection)} </div>  ); }; 
Enter fullscreen mode Exit fullscreen mode

Intelligent Lazy Loading

Our LazySection component uses Intersection Observer for viewport-aware loading:

const LazySection: React.FC<LazySectionProps> = ({ Component, skeleton, sectionName }) => { const [shouldRender, setShouldRender] = useState(false); const targetRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!targetRef.current) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setShouldRender(true); performance.mark(`section-${sectionName}-start`); } }, { threshold: 0.1, rootMargin: '50px' // Start loading 50px before visible } ); observer.observe(targetRef.current); return () => observer.disconnect(); }, [sectionName]); return ( <div ref={targetRef} data-section={sectionName}> {shouldRender ? ( <Suspense fallback={<SectionSkeleton config={skeleton} />}>  <Component /> </Suspense>  ) : ( <SectionSkeleton config={skeleton} />  )} </div>  ); }; 
Enter fullscreen mode Exit fullscreen mode

Content-Aware Skeleton System

Each section defines its own loading state:

const SectionSkeleton: React.FC<{ config: SkeletonConfig }> = ({ config }) => { switch (config.type) { case 'grid': const items = (config.cols || 3) * (config.rows || 2); return ( <div className={`grid grid-cols-${config.cols} gap-4`}> {Array.from({ length: items }).map((_, i) => ( <Skeleton key={i} className="h-32" delay={i * 50} />  ))} </div>  ); case 'carousel': return ( <div className="flex gap-4 overflow-hidden"> {Array.from({ length: 3 }).map((_, i) => ( <Skeleton key={i} className="flex-shrink-0 w-80 h-48" /> ))} </div>  ); // ... more skeleton types } }; 
Enter fullscreen mode Exit fullscreen mode

Real Implementation Examples {#implementation}

Our GenAI Document Analyzer Page

export const GENAI_SECTIONS: Section[] = [ createSection( 'hero', () => import('@/components/sections/genai/HeroSection'), { type: 'banner', height: '600px' } ), createSection( 'processing-modes', () => import('@/components/sections/genai/AIProcessingModes'), { type: 'grid', cols: 2, rows: 4 } ), createSection( 'industry-solutions', () => import('@/components/sections/genai/IndustrySolutions'), { type: 'carousel', items: 4 } ), createSection( 'testimonials', () => import('@/components/sections/common/ClientTestimonials'), { type: 'carousel', autoPlay: true } ) ]; // The entire page export default function GenAIPage() { return ( <PageRenderer sections={GENAI_SECTIONS} pagelink="genai" globalProps={{ theme: 'dark' }} />  ); } 
Enter fullscreen mode Exit fullscreen mode

Dynamic Configuration with Feature Flags

const getPageSections = (page: string, user?: User): Section[] => { const baseSections = PAGE_CONFIGS[page] || []; // A/B testing through configuration if (user?.experimentGroup === 'B') { return [ baseSections[1], // Move features first for group B baseSections[0], ...baseSections.slice(2) ]; } // Feature flags if (featureFlags.newTestimonials) { const newSections = [...baseSections]; const testimonialIndex = newSections.findIndex(s => s.id === 'testimonials'); if (testimonialIndex !== -1) { newSections[testimonialIndex] = createSection( 'testimonials-v2', () => import('./TestimonialsV2'), { type: 'masonry', cols: 3 } ); } } return baseSections; }; 
Enter fullscreen mode Exit fullscreen mode

Performance Impact {#performance}

The results speak for themselves:

Metric Before After Improvement
Bundle Size 450KB 157KB -65%
Time to Interactive 3.8s 2.3s -40%
First Contentful Paint 1.6s 0.8s -50%
Lighthouse Score 68 95+ +40%
Page Creation Time 2 hours 18 min -85%

Bundle Analysis Deep Dive

# Before - One massive chunk main.js 450KB vendors.js 1.2MB Total Initial: 1.65MB # After - Intelligent splitting  main.js 157KB framework.js 45KB commons.js 89KB sections/hero.js 23KB (loaded on demand) sections/services.js 34KB (loaded on demand) Total Initial: 291KB 
Enter fullscreen mode Exit fullscreen mode

Real User Metrics

  • Bounce Rate: Decreased from 34% to 21%
  • Session Duration: Increased from 2.3min to 3.8min
  • Conversion Rate: Improved by 45%
  • Time to Interactive: 40% faster across all devices

Developer Experience Revolution {#dx-revolution}

Before vs After: New Developer Onboarding

Before (2 weeks):

  • Day 1-3: Understanding component architecture
  • Day 4-7: Learning data fetching patterns
  • Day 8-10: Understanding error handling
  • Day 11-14: First page creation

After (3 days):

  • Day 1: Configuration pattern explanation (2 hours)
  • Day 2: Practice with existing sections
  • Day 3: Create first page independently

Code Review Time Reduction

// Old way - 200+ line page file // - Check imports // - Verify data fetching // - Validate error handling  // - Review prop passing // Average review time: 45 minutes // New way - 10 line configuration const SECTIONS = [ createSection('hero', heroImport, heroSkeleton), createSection('features', featuresImport, featuresSkeleton) ]; // Average review time: 5 minutes 
Enter fullscreen mode Exit fullscreen mode

Developer Happiness Metrics

We surveyed our development team:

  • "I enjoy working on pages": 23% → 89%
  • "I understand the codebase": 45% → 91%
  • "I can work independently": 34% → 87%
  • "Code reviews are helpful": 56% → 94%

Lessons Learned {#lessons}

1. Configuration > Code for Repetitive Patterns

When you find yourself copying code, ask: "Can this be configuration?"

2. Performance Should Be Automatic

Developers shouldn't think about:

  • Lazy loading (automatic with sections)
  • Code splitting (handled by dynamic imports)
  • Loading states (defined in skeleton config)

3. Error Boundaries at Every Level

// Section-level errors don't crash pages <SectionErrorBoundary sectionName="testimonials"> <TestimonialsSection /> </SectionErrorBoundary> 
Enter fullscreen mode Exit fullscreen mode

4. Developer Experience Drives Product Quality

Happy developers → Better code → Better user experience

5. Migration Strategy: One Page at a Time

We migrated our entire application over 3 months:

  • Week 1-4: Build PageRenderer system
  • Week 5-8: Convert homepage + 2 key pages
  • Week 9-12: Migrate remaining pages
  • Zero downtime, minimal risk

Advanced Patterns

Context-Aware Data Fetching

// Components automatically get the right data const TestimonialsSection = ({ pagelink }) => { // Automatically selects correct API based on pagelink const { data } = useTestimonials(pagelink); return <TestimonialCarousel data={data} />; }; // Hook mapping const useTestimonials = createHookMap({ 'genai': useGenAiTestimonials, 'mobile-app': useMobileAppTestimonials, 'default': useGeneralTestimonials }, 'default'); 
Enter fullscreen mode Exit fullscreen mode

Multi-Level Error Recovery

// Automatic retry for transient errors const SectionErrorBoundary = ({ children, sectionName }) => { const [retryCount, setRetryCount] = useState(0); const handleError = (error) => { const errorType = classifyError(error); if (errorType.autoRetry && retryCount < 3) { setTimeout(() => { setRetryCount(prev => prev + 1); // Component automatically retries }, errorType.retryDelay); } }; return ( <ErrorBoundary onError={handleError}> {children} </ErrorBoundary>  ); }; 
Enter fullscreen mode Exit fullscreen mode

Future Innovations

AI-Powered Page Optimization

We're exploring ML models that optimize page configurations based on user behavior:

// Future: AI suggests optimal section arrangements const optimizedSections = await optimizePageLayout({ baseSections: HOMEPAGE_SECTIONS, userSegment: currentUser.segment, conversionGoal: 'signup', timeOfDay: new Date().getHours() }); 
Enter fullscreen mode Exit fullscreen mode

Real-Time Configuration Updates

// Deploy new page layouts without code changes const sections = await fetchSectionsFromCMS(pageName); return <PageRenderer sections={sections} />; 
Enter fullscreen mode Exit fullscreen mode

Conclusion

Configuration-driven architecture isn't just a pattern—it's a paradigm shift. By treating pages as data structures, we unlocked:

  • Unprecedented flexibility (A/B testing through config)
  • Automatic performance (lazy loading built-in)
  • Developer happiness (simple mental model)
  • Business agility (rapid iteration)

The best part? You can start small. Convert one page to this pattern, measure the impact, then expand.

Remember: The goal isn't to build software. It's to solve problems efficiently.


Resources

Connect with me:

What patterns have transformed your development experience? Share in the comments below!

Top comments (0)