Scalable Responsive Design · How it works

1 min read
Mid-level1 min read
Rapid overview

How it works

Part 1: Scalable Frontend Architecture

What is Frontend Scalability?

Frontend scalability refers to the application's ability to handle:

  • User Growth: More concurrent users
  • Feature Growth: More features without performance degradation
  • Team Growth: More developers working simultaneously
  • Data Growth: Larger datasets and state
  • Device Diversity: Different screen sizes and capabilities

Scalability Dimensions

1. Performance Scalability

Handle increased load without degradation.

// Bad: Loads everything upfront
import AllComponents from './components';
import AllUtilities from './utils';
import AllPages from './pages';

// Good: Code splitting
const HomePage = lazy(() => import('./pages/HomePage'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));

2. Team Scalability

Multiple teams work without blocking each other.

project/
├── packages/
│   ├── design-system/        # Shared components
│   ├── shared-utils/          # Common utilities
│   ├── team-a-features/       # Team A owns these
│   ├── team-b-features/       # Team B owns these
│   └── shell-app/             # Composition layer

3. Maintainability Scalability

Codebase remains manageable as it grows.

// Bad: God component
function Dashboard() {
  // 1000+ lines of code
  // Multiple responsibilities
  // Hard to test
}

// Good: Composed components
function Dashboard() {
  return (
    <DashboardLayout>
      <MetricsPanel />
      <RecentActivity />
      <QuickActions />
    </DashboardLayout>
  );
}

Scalable Code Organization

Feature-Based Structure

src/
├── features/
│   ├── authentication/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── types/
│   │   └── index.ts
│   ├── products/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── types/
│   │   └── index.ts
│   └── checkout/
│       ├── components/
│       ├── hooks/
│       ├── services/
│       ├── types/
│       └── index.ts
├── shared/
│   ├── components/
│   ├── hooks/
│   ├── utils/
│   └── types/
└── app/
    ├── routes/
    ├── layout/
    └── App.tsx

Benefits:

  • Features are self-contained
  • Easy to locate code
  • Clear ownership
  • Can be extracted to packages

Layer-Based Architecture

src/
├── presentation/        # UI components
│   ├── pages/
│   └── components/
├── domain/             # Business logic
│   ├── entities/
│   ├── use-cases/
│   └── services/
├── data/               # Data access
│   ├── repositories/
│   ├── api/
│   └── cache/
└── infrastructure/     # External concerns
    ├── logging/
    ├── analytics/
    └── config/

State Management at Scale

Local vs Global State

// Bad: Everything in global state
const globalStore = {
  user: {...},
  products: [...],
  modalOpen: false,  // UI state shouldn't be global
  hoverState: {...}, // Transient state shouldn't be global
  formData: {...}    // Form state shouldn't be global
};

// Good: Appropriate state placement
// Global state (shared across app)
const useAuthStore = create((set) => ({
  user: null,
  isAuthenticated: false,
  login: (credentials) => {...},
  logout: () => {...}
}));

// Local state (component-specific)
function ProductCard() {
  const [isHovered, setIsHovered] = useState(false);
  const [quantity, setQuantity] = useState(1);
  // ...
}

// Feature state (shared within feature)
const useProductsStore = create((set) => ({
  products: [],
  selectedCategory: null,
  filters: {}
}));

State Colocation

// Bad: State far from usage
function App() {
  const [modalOpen, setModalOpen] = useState(false);
  const [selectedProduct, setSelectedProduct] = useState(null);

  return (
    <div>
      <Header />
      <ProductList
        onProductSelect={setSelectedProduct}
        onModalOpen={() => setModalOpen(true)}
      />
      {/* Prop drilling */}
    </div>
  );
}

// Good: State close to usage
function ProductList() {
  const [selectedProduct, setSelectedProduct] = useState(null);
  const [modalOpen, setModalOpen] = useState(false);

  return (
    <>
      <ProductGrid onProductClick={setSelectedProduct} />
      {modalOpen && <ProductModal product={selectedProduct} />}
    </>
  );
}

Data Fetching at Scale

Caching and Deduplication

// Using React Query for scalable data fetching
import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';

// Configure caching strategy
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,  // 5 minutes
      cacheTime: 10 * 60 * 1000, // 10 minutes
      refetchOnWindowFocus: false,
      retry: 3
    }
  }
});

// Fetch with automatic caching
function useProducts() {
  return useQuery({
    queryKey: ['products'],
    queryFn: fetchProducts,
    // Automatically deduplicates requests
    // Multiple components can call this without extra network calls
  });
}

// Prefetch for better UX
function ProductsPage() {
  const queryClient = useQueryClient();

  useEffect(() => {
    // Prefetch next page
    queryClient.prefetchQuery({
      queryKey: ['products', page + 1],
      queryFn: () => fetchProducts(page + 1)
    });
  }, [page]);

  return <ProductList />;
}

Pagination and Infinite Scroll

// Efficient pagination
function useProductsPaginated(page: number, pageSize: number) {
  return useQuery({
    queryKey: ['products', page, pageSize],
    queryFn: () => fetchProducts({ page, pageSize }),
    keepPreviousData: true  // Prevent loading flicker
  });
}

// Infinite scroll
import { useInfiniteQuery } from '@tanstack/react-query';

function useProductsInfinite() {
  return useInfiniteQuery({
    queryKey: ['products'],
    queryFn: ({ pageParam = 0 }) => fetchProducts(pageParam),
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length : undefined;
    }
  });
}

function ProductList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useProductsInfinite();

  return (
    <>
      {data?.pages.map(page =>
        page.products.map(product => (
          <ProductCard key={product.id} product={product} />
        ))
      )}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()}>
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </>
  );
}

Performance Optimization Patterns

Virtualization for Large Lists

import { FixedSizeList } from 'react-window';

function LargeProductList({ products }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={products.length}
      itemSize={100}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <ProductCard product={products[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}

Memoization

import { memo, useMemo, useCallback } from 'react';

// Memoize expensive components
const ProductCard = memo(({ product, onAddToCart }) => {
  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={() => onAddToCart(product)}>Add to Cart</button>
    </div>
  );
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.product.id === nextProps.product.id &&
         prevProps.product.price === nextProps.product.price;
});

// Memoize expensive calculations
function ProductList({ products, category }) {
  const filteredProducts = useMemo(() => {
    return products.filter(p => p.category === category);
  }, [products, category]);

  const handleAddToCart = useCallback((product) => {
    // Add to cart logic
  }, []);

  return filteredProducts.map(product => (
    <ProductCard
      key={product.id}
      product={product}
      onAddToCart={handleAddToCart}
    />
  ));
}

Code Splitting Strategies

// Route-based splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const Settings = lazy(() => import('./pages/Settings'));

// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function AnalyticsPage() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

// Library splitting (load on demand)
async function exportToExcel(data) {
  const XLSX = await import('xlsx');
  const worksheet = XLSX.utils.json_to_sheet(data);
  // ...
}

Bundle Optimization

Analyzing Bundle Size

# Using webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer

# Add to webpack config
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

Tree Shaking

// Bad: Imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);

// Good: Import only what you need
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);

// Even better: Use modern alternatives
import { debounce } from 'lodash-es';  // ES modules version

Dynamic Imports

// Conditional loading
async function loadFeature(featureName: string) {
  if (featureName === 'advanced-charts') {
    const { AdvancedCharts } = await import('./features/AdvancedCharts');
    return AdvancedCharts;
  }
}

// User role-based loading
function AdminPanel() {
  const { isAdmin } = useAuth();

  if (!isAdmin) return null;

  const AdminTools = lazy(() => import('./AdminTools'));

  return (
    <Suspense fallback={<div>Loading admin tools...</div>}>
      <AdminTools />
    </Suspense>
  );
}

Part 2: Responsive Design

Mobile-First Approach

/* Mobile-first: Start with mobile styles */
.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 1rem;
}

/* Tablet: 768px and up */
@media (min-width: 768px) {
  .product-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.5rem;
    padding: 1.5rem;
  }
}

/* Desktop: 1024px and up */
@media (min-width: 1024px) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
    padding: 2rem;
  }
}

/* Large desktop: 1440px and up */
@media (min-width: 1440px) {
  .product-grid {
    grid-template-columns: repeat(4, 1fr);
    max-width: 1400px;
    margin: 0 auto;
  }
}

Responsive Breakpoints

// Define consistent breakpoints
export const breakpoints = {
  xs: '0px',
  sm: '640px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
  '2xl': '1536px'
};

// Use in styled components
import styled from 'styled-components';

const Container = styled.div`
  padding: 1rem;

  @media (min-width: ${breakpoints.md}) {
    padding: 2rem;
  }

  @media (min-width: ${breakpoints.lg}) {
    padding: 3rem;
    max-width: 1200px;
    margin: 0 auto;
  }
`;

// Or use CSS custom properties
:root {
  --breakpoint-sm: 640px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 1024px;
  --breakpoint-xl: 1280px;
}

Responsive Hooks

// useMediaQuery hook
function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    if (media.matches !== matches) {
      setMatches(media.matches);
    }

    const listener = () => setMatches(media.matches);
    media.addEventListener('change', listener);

    return () => media.removeEventListener('change', listener);
  }, [matches, query]);

  return matches;
}

// Usage
function ProductGrid() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
  const isDesktop = useMediaQuery('(min-width: 1025px)');

  const columns = isMobile ? 1 : isTablet ? 2 : 4;

  return <Grid columns={columns}>{/* ... */}</Grid>;
}

// useBreakpoint hook
function useBreakpoint() {
  const [breakpoint, setBreakpoint] = useState('md');

  useEffect(() => {
    const updateBreakpoint = () => {
      const width = window.innerWidth;
      if (width < 640) setBreakpoint('xs');
      else if (width < 768) setBreakpoint('sm');
      else if (width < 1024) setBreakpoint('md');
      else if (width < 1280) setBreakpoint('lg');
      else setBreakpoint('xl');
    };

    updateBreakpoint();
    window.addEventListener('resize', updateBreakpoint);
    return () => window.removeEventListener('resize', updateBreakpoint);
  }, []);

  return breakpoint;
}

// Usage
function Navigation() {
  const breakpoint = useBreakpoint();

  return breakpoint === 'xs' || breakpoint === 'sm'
    ? <MobileNav />
    : <DesktopNav />;
}

Responsive Images

// Picture element for art direction
function ResponsiveHero() {
  return (
    <picture>
      <source
        media="(min-width: 1024px)"
        srcSet="/images/hero-desktop.jpg"
      />
      <source
        media="(min-width: 768px)"
        srcSet="/images/hero-tablet.jpg"
      />
      <img
        src="/images/hero-mobile.jpg"
        alt="Hero banner"
        loading="lazy"
      />
    </picture>
  );
}

// Responsive background images
.hero {
  background-image: url('/images/hero-mobile.jpg');
  background-size: cover;
  background-position: center;
}

@media (min-width: 768px) {
  .hero {
    background-image: url('/images/hero-tablet.jpg');
  }
}

@media (min-width: 1024px) {
  .hero {
    background-image: url('/images/hero-desktop.jpg');
  }
}

// Srcset for resolution switching
<img
  src="/images/product-800.jpg"
  srcSet="
    /images/product-400.jpg 400w,
    /images/product-800.jpg 800w,
    /images/product-1200.jpg 1200w
  "
  sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
  alt="Product"
  loading="lazy"
/>

Responsive Typography

/* Fluid typography using clamp() */
h1 {
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.2;
}

h2 {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

p {
  font-size: clamp(1rem, 2vw, 1.125rem);
  line-height: 1.6;
}

/* Responsive spacing */
.container {
  padding: clamp(1rem, 3vw, 3rem);
  margin-bottom: clamp(2rem, 5vw, 5rem);
}

/* CSS custom properties for responsive values */
:root {
  --font-size-base: 16px;
  --spacing-unit: 8px;
}

@media (min-width: 768px) {
  :root {
    --font-size-base: 18px;
    --spacing-unit: 12px;
  }
}

@media (min-width: 1024px) {
  :root {
    --font-size-base: 20px;
    --spacing-unit: 16px;
  }
}

Container Queries

/* New: Container queries (experimental) */
.card-container {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: flex;
  flex-direction: column;
}

/* Adjust layout based on container width, not viewport */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;
  }

  .card-image {
    width: 40%;
  }

  .card-content {
    width: 60%;
  }
}

Responsive Utilities

// Responsive utility system
const responsive = {
  mobile: (...styles) => `
    @media (max-width: 767px) {
      ${styles.join('\n')}
    }
  `,
  tablet: (...styles) => `
    @media (min-width: 768px) and (max-width: 1023px) {
      ${styles.join('\n')}
    }
  `,
  desktop: (...styles) => `
    @media (min-width: 1024px) {
      ${styles.join('\n')}
    }
  `
};

// Usage with styled-components
const Button = styled.button`
  padding: 0.5rem 1rem;
  font-size: 0.875rem;

  ${responsive.tablet`
    padding: 0.75rem 1.5rem;
    font-size: 1rem;
  `}

  ${responsive.desktop`
    padding: 1rem 2rem;
    font-size: 1.125rem;
  `}
`;

Best Practices

1. Performance Budget

// Define performance budgets
const budgets = {
  maxBundleSize: 250 * 1024,      // 250KB
  maxInitialLoad: 3000,            // 3 seconds
  maxTimeToInteractive: 5000,     // 5 seconds
  maxImageSize: 500 * 1024        // 500KB
};

// Monitor in CI/CD
if (bundleSize > budgets.maxBundleSize) {
  throw new Error(`Bundle size ${bundleSize} exceeds budget ${budgets.maxBundleSize}`);
}

2. Accessibility

// Ensure responsive design is accessible
function ResponsiveNav() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return isMobile ? (
    <button
      aria-label="Open navigation menu"
      aria-expanded={menuOpen}
      onClick={toggleMenu}
    >
      <MenuIcon />
    </button>
  ) : (
    <nav aria-label="Main navigation">
      <NavLinks />
    </nav>
  );
}

3. Testing

// Test responsive behavior
import { render } from '@testing-library/react';
import { useMediaQuery } from './hooks/useMediaQuery';

// Mock window.matchMedia
beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: jest.fn().mockImplementation(query => ({
      matches: query === '(max-width: 768px)',
      media: query,
      addEventListener: jest.fn(),
      removeEventListener: jest.fn()
    }))
  });
});

test('renders mobile view on small screens', () => {
  const { getByTestId } = render(<ResponsiveComponent />);
  expect(getByTestId('mobile-view')).toBeInTheDocument();
});

See also