Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/ec1f145c634a349a0c39d72386750d3a to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/ec1f145c634a349a0c39d72386750d3a to your computer and use it in GitHub Desktop.
Web Performance, Security & Accessibility - Interview Ready Guide

Web Performance, Security & Accessibility - Interview Ready Guide

Table of Contents

Performance

  1. Network & Delivery
  2. JavaScript Performance
  3. React-Specific Optimizations
  4. CSS & Rendering
  5. Images & Media
  6. Browser Internals
  7. Core Web Vitals
  8. Caching & Offline
  9. Performance Measurement

Security

  1. XSS (Cross-Site Scripting)
  2. CSRF (Cross-Site Request Forgery)
  3. CORS (Cross-Origin Resource Sharing)
  4. Authentication & Authorization
  5. JWT Security
  6. Clickjacking
  7. Content Security Policy
  8. HTTPS & MITM
  9. Secure Cookies
  10. Frontend Security Best Practices

Accessibility

  1. WCAG Principles (POUR)
  2. Semantic HTML vs ARIA
  3. Keyboard Accessibility
  4. Color & Contrast
  5. Accessible React Patterns
  6. Forms Accessibility
  7. Testing Tools

Quick Reference

  1. One-Line Interview Answers

Performance Optimization

Theory: Why Performance Matters

Web performance directly impacts user experience, conversion rates, and SEO rankings. Studies show:

  • 53% of mobile users abandon sites that take longer than 3 seconds to load
  • 1 second delay can reduce conversions by 7%
  • Google uses performance as a ranking factor

The key is understanding the critical rendering path and optimizing each stage.


1. Network & Delivery (Biggest Wins)

Theory

The network is often the biggest bottleneck. Every byte transmitted costs time, especially on slow connections. HTTP requests have overhead (DNS lookup, TCP handshake, TLS negotiation), so reducing requests and payload size yields massive gains.

Goal: Send less, send faster.

✅ Reduce Payload

Minify + Compress

  • Minification: Removes whitespace, comments, shortens variable names
  • Gzip: ~70% compression, universally supported
  • Brotli: ~80% compression, better for static assets, requires HTTPS
Content-Encoding: br

Remove Unused JS/CSS (Tree-shaking)

  • Dead code elimination at build time
  • Works with ES6 modules
  • Webpack, Rollup, Vite do this automatically

Avoid Huge JSON Responses

  • Paginate API responses
  • Use GraphQL to request only needed fields
  • Consider compression for large payloads

✅ HTTP Optimizations

HTTP/2 / HTTP/3

  • Multiplexing: Multiple requests over single connection
  • Header compression: Reduces overhead
  • Server push: Proactively send resources
  • HTTP/3: Built on QUIC, faster connection establishment

CDN for Static Assets

  • Geographically distributed servers
  • Lower latency (closer to users)
  • Edge caching
  • Offloads origin server

Cache Aggressively

Cache-Control: public, max-age=31536000, immutable
  • public: Can be cached by CDNs
  • max-age: Time in seconds
  • immutable: Never revalidate (for versioned assets)

✅ Preloading & Resource Hints

<!-- Preload critical resources -->
<link rel="preload" as="script" href="/app.js">
<link rel="preload" as="style" href="/critical.css">
<link rel="preload" as="font" href="/font.woff2" crossorigin>

<!-- Establish early connections -->
<link rel="preconnect" href="https://cdn.com">
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
  • preload: High-priority fetch for current page
  • preconnect: Warm up connection (DNS + TCP + TLS)
  • dns-prefetch: DNS resolution only (fallback for older browsers)

2. JavaScript Performance

Theory

JavaScript is the most expensive resource on modern web. It must be downloaded, parsed, compiled, and executed - all on the main thread. Large JS bundles block rendering and delay interactivity.

Goal: Parse & execute less JS.

✅ Code Splitting

Break large bundles into smaller chunks loaded on-demand.

Route-based Splitting

const Dashboard = React.lazy(() => import("./Dashboard"));
const Profile = React.lazy(() => import("./Profile"));

<Suspense fallback={<Loading />}>
  <Routes>
    <Route path="/dashboard" element={<Dashboard />} />
  </Routes>
</Suspense>

Component-based Splitting

const HeavyChart = React.lazy(() => import("./HeavyChart"));

✅ Defer Non-Critical JS

<!-- Deferred: downloads in parallel, executes after HTML parsing -->
<script src="analytics.js" defer></script>

<!-- Async: downloads in parallel, executes immediately -->
<script src="ads.js" async></script>

When to use:

  • defer: Scripts that need DOM (most scripts)
  • async: Independent scripts (analytics, ads)

✅ Avoid Main-Thread Blocking

Long Tasks (>50ms) freeze the UI.

Break Long Tasks

// Bad: blocks for 500ms
for (let i = 0; i < 100000; i++) {
  processItem(i);
}

// Good: yields to browser
async function processInChunks() {
  for (let i = 0; i < 100000; i += 100) {
    for (let j = 0; j < 100; j++) {
      processItem(i + j);
    }
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

requestIdleCallback

requestIdleCallback(() => {
  // Non-urgent work
  prefetchNextPage();
});

Web Workers

// main.js
const worker = new Worker('heavy.js');
worker.postMessage({ data });
worker.onmessage = (e) => updateUI(e.data);

// heavy.js
self.onmessage = (e) => {
  const result = expensiveComputation(e.data);
  self.postMessage(result);
};

3. React-Specific Optimizations

Theory

React's virtual DOM is efficient, but unnecessary re-renders still waste CPU. Each render involves executing component functions, diffing virtual DOM, and potentially updating real DOM.

Goal: Fewer renders, cheaper renders.

✅ Prevent Unnecessary Re-Renders

React.memo - Memoizes component

const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{expensiveComputation(data)}</div>;
});

useCallback - Memoizes functions

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

useMemo - Memoizes values

const sortedData = useMemo(() => {
  return data.sort((a, b) => a.value - b.value);
}, [data]);

✅ Prioritize UI Updates (React 18)

startTransition - Marks updates as non-urgent

import { startTransition } from 'react';

function handleSearch(query) {
  setInputValue(query); // Urgent

  startTransition(() => {
    setSearchResults(search(query)); // Non-urgent
  });
}

✅ Defer Expensive UI

useDeferredValue - Shows stale value while computing new one

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const results = search(deferredQuery);

  return (
    <div style={{ opacity: query !== deferredQuery ? 0.5 : 1 }}>
      {results.map(r => <Item key={r.id} {...r} />)}
    </div>
  );
}

✅ Virtualization for Large Lists

Only render visible items.

import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={600}
  itemCount={10000}
  itemSize={50}
  width="100%"
>
  {({ index, style }) => (
    <div style={style}>Item {index}</div>
  )}
</FixedSizeList>

Libraries:

  • react-window (lightweight)
  • react-virtualized (feature-rich)

4. CSS & Rendering

Theory

The browser rendering pipeline: Layout → Paint → Composite. Some CSS properties trigger expensive recalculations of the entire layout. Understanding which operations are cheap vs expensive is critical.

Goal: Avoid layout thrashing & minimize repaint cost.

✅ Critical CSS

Inline Above-the-Fold CSS

  • Prevents render-blocking
  • First paint happens faster
<head>
  <style>
    /* Critical CSS inlined */
    .hero { background: blue; }
  </style>
  <link rel="preload" href="/full.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>

✅ Avoid Layout Triggers

Expensive (triggers layout):

// ❌ Forces reflow
el.style.top = "10px";
el.style.width = "100px";

Cheap (compositor only):

// ✅ Only compositing
el.style.transform = "translateY(10px)";
el.style.opacity = 0.5;

Property Costs:

  • Layout: width, height, margin, padding, top, left, font-size
  • Paint: color, background, box-shadow
  • Composite only: transform, opacity

✅ Use will-change Carefully

.card {
  will-change: transform;
}

Purpose: Creates a new composite layer before animation starts.

Warning: Overuse wastes memory. Remove after animation:

el.style.willChange = 'transform';
el.addEventListener('animationend', () => {
  el.style.willChange = 'auto';
});

5. Images & Media

Theory

Images typically account for 50%+ of page weight. Modern formats and lazy loading can cut this dramatically with minimal effort.

Goal: Smaller images, loaded later.

✅ Modern Formats

  • WebP: ~30% smaller than JPEG, wide support
  • AVIF: ~50% smaller, growing support
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Fallback">
</picture>

✅ Responsive Images

<img
  src="small.jpg"
  srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1024w"
  sizes="(max-width: 600px) 480px, (max-width: 900px) 768px, 1024px"
  alt="Responsive image"
/>
  • srcset: Available image sizes
  • sizes: Expected display size
  • Browser picks best option

✅ Lazy Loading

<img src="image.jpg" loading="lazy" alt="Lazy loaded">

Native lazy loading delays download until near viewport.

Eager vs Lazy:

  • Above-the-fold: loading="eager" (default)
  • Below-the-fold: loading="lazy"

6. Browser Internals

Theory: Critical Rendering Path

Understanding how browsers render pages helps you optimize effectively.

The Pipeline:

  1. HTML → DOM

    • Browser parses HTML into DOM tree
  2. CSS → CSSOM

    • Browser parses CSS into CSSOM tree
  3. DOM + CSSOM → Render Tree

    • Combines trees, excludes hidden elements
  4. Layout

    • Calculates exact position and size
  5. Paint

    • Fills in pixels (text, colors, images, shadows)
  6. Composite

    • Combines layers in correct order

Blocking Resources

🔴 Render-blocking:

  • CSS blocks rendering (needs CSSOM before render tree)
  • Synchronous JS blocks parsing

🟢 Non-blocking:

  • async / defer scripts
  • Images, fonts (don't block render)

Optimization Strategy

<!-- Critical CSS inline -->
<style>/* Critical styles */</style>

<!-- Async load full CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">

<!-- Defer non-critical JS -->
<script src="app.js" defer></script>

7. Core Web Vitals

Theory

Google's Core Web Vitals measure real user experience. They directly impact SEO and conversion. Understanding these metrics and how to improve them is essential for modern web development.

The Three Core Metrics

Metric Meaning Good Needs Improvement Poor
LCP Largest Contentful Paint ≤ 2.5s 2.5s - 4s > 4s
INP Interaction to Next Paint ≤ 200ms 200ms - 500ms > 500ms
CLS Cumulative Layout Shift ≤ 0.1 0.1 - 0.25 > 0.25

1. LCP – Largest Contentful Paint

What: Time until the largest visible element loads

Measures: Loading performance

Common Causes:

  • Slow server response (TTFB)
  • Large, unoptimized images
  • Render-blocking JavaScript/CSS
  • Client-side rendering delays

How to Fix:

<!-- Optimize images -->
<img src="hero.webp" fetchpriority="high" alt="Hero">

<!-- Preload critical resources -->
<link rel="preload" as="image" href="hero.webp">

<!-- Use CDN -->
<img src="https://cdn.example.com/hero.webp">

Server-side:

  • Reduce TTFB with caching, CDN
  • Use SSR or SSG for critical content
  • Optimize database queries

2. INP – Interaction to Next Paint

What: Delay between user interaction and UI update (replaced FID in 2024)

Measures: Responsiveness

Common Causes:

  • Long JavaScript tasks (>50ms)
  • Heavy event handlers
  • Expensive re-renders
  • Main thread blocking

How to Fix:

// Break long tasks
async function processLargeList(items) {
  for (let i = 0; i < items.length; i += 50) {
    processBatch(items.slice(i, i + 50));
    await scheduler.yield(); // Let browser breathe
  }
}

// Use React 18 transitions
import { startTransition } from 'react';

function handleFilter(value) {
  setInputValue(value); // Immediate
  startTransition(() => {
    setFilteredResults(filter(value)); // Deferred
  });
}

// Debounce expensive operations
const debouncedSearch = useDeferredValue(searchQuery);

Code splitting:

const HeavyComponent = lazy(() => import('./Heavy'));

3. CLS – Cumulative Layout Shift

What: Unexpected layout movement while page loads

Measures: Visual stability

Common Causes:

  • Images without dimensions
  • Ads, embeds, iframes injected dynamically
  • Web fonts causing FOUT/FOIT
  • Animations that trigger layout

How to Fix:

<!-- Always set dimensions -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

<!-- Or use aspect-ratio -->
<img src="photo.jpg" style="aspect-ratio: 16/9; width: 100%;">

<!-- Reserve space for ads -->
<div class="ad-slot" style="min-height: 250px;">
  <!-- Ad loads here -->
</div>

<!-- Prevent font flash -->
<link rel="preload" href="font.woff2" as="font" crossorigin>
<style>
  @font-face {
    font-family: 'MyFont';
    font-display: swap; /* or optional */
    src: url('font.woff2');
  }
</style>

CSS transforms (don't trigger layout):

/* ❌ Causes layout shift */
.element {
  top: 0;
  transition: top 0.3s;
}
.element:hover {
  top: -10px;
}

/* ✅ No layout shift */
.element {
  transform: translateY(0);
  transition: transform 0.3s;
}
.element:hover {
  transform: translateY(-10px);
}

Supporting Web Vitals

TTFB – Time to First Byte

  • Server response time
  • Target: < 800ms
  • Improve with: caching, CDN, database optimization

FCP – First Contentful Paint

  • First pixel rendered
  • Target: < 1.8s
  • Improve with: inline critical CSS, reduce blocking resources

TBT – Total Blocking Time

  • Lab metric (Lighthouse)
  • Sum of blocking time from long tasks
  • Correlates with INP

How to Measure

Field Data (Real Users):

import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

// Send to analytics
function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  navigator.sendBeacon('/analytics', body);
}

onLCP(sendToAnalytics);

Lab Data (Testing):

  • Lighthouse in Chrome DevTools
  • Chrome UX Report (CrUX) - real user data
  • PageSpeed Insights - combines lab + field data
  • WebPageTest - detailed waterfall analysis

Tools:

  • Chrome DevTools → Performance tab
  • Google Search Console → Core Web Vitals report
  • web.dev/measure

8. Caching & Offline

Theory

Caching eliminates network requests entirely - the fastest request is no request. Service Workers enable sophisticated caching strategies and offline experiences.

Service Workers

// sw.js - Cache-first strategy
self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/app.js',
        '/logo.png'
      ]);
    })
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then(response => {
      return response || fetch(e.request);
    })
  );
});

Caching Strategies

1. Cache First

// Try cache, fallback to network
caches.match(request) || fetch(request)

Use for: Static assets, images

2. Network First

// Try network, fallback to cache
fetch(request).catch(() => caches.match(request))

Use for: API calls, dynamic content

3. Stale-While-Revalidate

const cached = await caches.match(request);
const fetching = fetch(request).then(res => {
  cache.put(request, res.clone());
  return res;
});
return cached || fetching;

Use for: Balance freshness & speed

IndexedDB for Large Data

const db = await openDB('my-db', 1, {
  upgrade(db) {
    db.createObjectStore('posts');
  }
});

await db.put('posts', data, 'post-123');
const post = await db.get('posts', 'post-123');

9. Measuring Performance

Theory

"You can't improve what you don't measure." Performance monitoring should be continuous, not one-time.

Tools

Chrome DevTools → Performance Tab

  • Record real interactions
  • See flame charts, long tasks
  • Identify bottlenecks

Lighthouse

  • Automated audits
  • Performance score + suggestions
  • Built into Chrome DevTools

WebPageTest

  • Test from different locations
  • Connection throttling
  • Filmstrip view, waterfall

React Profiler

import { Profiler } from 'react';

<Profiler id="App" onRender={callback}>
  <App />
</Profiler>

function callback(id, phase, actualDuration) {
  console.log(`${id} took ${actualDuration}ms`);
}

What to Look For

Long Tasks (>50ms)

  • Block user interactions
  • Split into smaller chunks

JS Parse/Execute Time

  • Large bundles slow startup
  • Code split aggressively

Layout Shifts

  • Measure CLS
  • Set image dimensions

Network Waterfalls

  • Identify blocking resources
  • Check compression, caching
  • Look for request chains

Performance Budget

{
  "budgets": [{
    "resourceType": "script",
    "budget": 300
  }, {
    "resourceType": "total",
    "budget": 500
  }]
}

Set budgets, fail builds if exceeded.


Web Security

Theory: Defense in Depth

Security is layered. No single measure is perfect, but combining multiple strategies creates robust protection. Always assume user input is malicious and verify everything server-side.


10. XSS (Cross-Site Scripting)

Theory

XSS allows attackers to inject malicious JavaScript into your site, running in the context of your domain with full access to cookies, localStorage, and the DOM.

Attack Vector: Attacker injects malicious JS into your site

Types

1. Stored XSS

  • Saved in database
  • Affects all users who view it
  • Example: Comment systems, user profiles

2. Reflected XSS

  • Via URL/query parameters
  • Affects single user (via phishing link)
  • Example: Search results, error messages

3. DOM XSS

  • Unsafe client-side JS manipulation
  • Never reaches server
  • Example: innerHTML, eval()

Vulnerable Example

// ❌ DANGEROUS
const userInput = "<img src=x onerror='alert(1)'>";
div.innerHTML = userInput; // XSS!

// ❌ DANGEROUS
const search = new URLSearchParams(location.search).get('q');
document.write(search); // XSS!

Prevention

1. Escape Output (React does this automatically)

// ✅ Safe - React escapes by default
<div>{userInput}</div>

// ❌ Dangerous - bypasses escaping
<div dangerouslySetInnerHTML={{__html: userInput}} />

2. Sanitize Input

import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(userInput);
div.innerHTML = clean; // ✅ Safe

3. Content Security Policy

Content-Security-Policy: default-src 'self'; script-src 'self'

4. Use textContent over innerHTML

// ✅ Safe
element.textContent = userInput;

// ❌ Dangerous
element.innerHTML = userInput;

11. CSRF (Cross-Site Request Forgery)

Theory

CSRF exploits the fact that browsers automatically send cookies with every request. An attacker tricks your browser into making authenticated requests to another site where you're logged in.

Attack Flow:

  1. User logs into bank.com
  2. User visits evil.com
  3. evil.com submits form to bank.com/transfer
  4. Browser sends cookies → request succeeds!

Example Attack

<!-- On evil.com -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>

Prevention

1. CSRF Tokens

// Server generates token
const token = generateCSRFToken();
res.cookie('csrf', token);

// Client includes in requests
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrf')
  }
});

// Server validates
if (req.headers['x-csrf-token'] !== req.cookies.csrf) {
  throw new Error('CSRF validation failed');
}

2. SameSite Cookies

Set-Cookie: sessionId=abc; SameSite=Strict; Secure
  • Strict: Never sent on cross-site requests
  • Lax: Sent on top-level GET navigation
  • None: Always sent (requires Secure)

3. Custom Headers (JWT approach)

// Browser won't send custom headers cross-origin
fetch('/api/data', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

12. CORS (Cross-Origin Resource Sharing)

Theory

CORS is a browser security mechanism, not server protection. It prevents malicious sites from reading responses from your API via JavaScript.

Key Point: CORS protects users, not your API.

How It Works

Browser → OPTIONS preflight → Server
Server → Access-Control-Allow-Origin → Browser
Browser → Actual request → Server (only if allowed)

Headers

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

Common Mistake

# ❌ INVALID - can't use * with credentials
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

# ✅ VALID
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

When CORS Doesn't Apply

  • Same-origin requests
  • <img>, <script>, <link> tags
  • Server-to-server requests

13. Authentication vs Authorization

Theory

| Concept | Meaning | Example | |---------|---------|---------|only:** transform, opacity

✅ Use will-change Carefully

.card {
  will-change: transform;
}

Purpose: Creates a new composite layer before animation starts.

Warning: Overuse wastes memory. Remove after animation:

el.style.willChange = 'transform';
el.addEventListener('animationend', () => {
  el.style.willChange = 'auto';
});

5. Images & Media

Theory

Images typically account for 50%+ of page weight. Modern formats and lazy loading can cut this dramatically with minimal effort.

Goal: Smaller images, loaded later.

✅ Modern Formats

  • WebP: ~30% smaller than JPEG, wide support
  • AVIF: ~50% smaller, growing support
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Fallback">
</picture>

✅ Responsive Images

<img
  src="small.jpg"
  srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1024w"
  sizes="(max-width: 600px) 480px, (max-width: 900px) 768px, 1024px"
  alt="Responsive image"
/>
  • srcset: Available image sizes
  • sizes: Expected display size
  • Browser picks best option

✅ Lazy Loading

<img src="image.jpg" loading="lazy" alt="Lazy loaded">

Native lazy loading delays download until near viewport.

Eager vs Lazy:

  • Above-the-fold: loading="eager" (default)
  • Below-the-fold: loading="lazy"

6. Browser Internals

Theory: Critical Rendering Path

Understanding how browsers render pages helps you optimize effectively.

The Pipeline:

  1. HTML → DOM

    • Browser parses HTML into DOM tree
  2. CSS → CSSOM

    • Browser parses CSS into CSSOM tree
  3. DOM + CSSOM → Render Tree

    • Combines trees, excludes hidden elements
  4. Layout

    • Calculates exact position and size
  5. Paint

    • Fills in pixels (text, colors, images, shadows)
  6. Composite

    • Combines layers in correct order

Blocking Resources

🔴 Render-blocking:

  • CSS blocks rendering (needs CSSOM before render tree)
  • Synchronous JS blocks parsing

🟢 Non-blocking:

  • async / defer scripts
  • Images, fonts (don't block render)

Optimization Strategy

<!-- Critical CSS inline -->
<style>/* Critical styles */</style>

<!-- Async load full CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">

<!-- Defer non-critical JS -->
<script src="app.js" defer></script>

7. Core Web Vitals

Theory

Google's Core Web Vitals measure real user experience. They directly impact SEO and conversion. Understanding these metrics and how to improve them is essential for modern web development.

The Three Core Metrics

Metric Meaning Good Needs Improvement Poor
LCP Largest Contentful Paint ≤ 2.5s 2.5s - 4s > 4s
INP Interaction to Next Paint ≤ 200ms 200ms - 500ms > 500ms
CLS Cumulative Layout Shift ≤ 0.1 0.1 - 0.25 > 0.25

1. LCP – Largest Contentful Paint

What: Time until the largest visible element loads

Measures: Loading performance

Common Causes:

  • Slow server response (TTFB)
  • Large, unoptimized images
  • Render-blocking JavaScript/CSS
  • Client-side rendering delays

How to Fix:

<!-- Optimize images -->
<img src="hero.webp" fetchpriority="high" alt="Hero">

<!-- Preload critical resources -->
<link rel="preload" as="image" href="hero.webp">

<!-- Use CDN -->
<img src="https://cdn.example.com/hero.webp">

Server-side:

  • Reduce TTFB with caching, CDN
  • Use SSR or SSG for critical content
  • Optimize database queries

2. INP – Interaction to Next Paint

What: Delay between user interaction and UI update (replaced FID in 2024)

Measures: Responsiveness

Common Causes:

  • Long JavaScript tasks (>50ms)
  • Heavy event handlers
  • Expensive re-renders
  • Main thread blocking

How to Fix:

// Break long tasks
async function processLargeList(items) {
  for (let i = 0; i < items.length; i += 50) {
    processBatch(items.slice(i, i + 50));
    await scheduler.yield(); // Let browser breathe
  }
}

// Use React 18 transitions
import { startTransition } from 'react';

function handleFilter(value) {
  setInputValue(value); // Immediate
  startTransition(() => {
    setFilteredResults(filter(value)); // Deferred
  });
}

// Debounce expensive operations
const debouncedSearch = useDeferredValue(searchQuery);

Code splitting:

const HeavyComponent = lazy(() => import('./Heavy'));

3. CLS – Cumulative Layout Shift

What: Unexpected layout movement while page loads

Measures: Visual stability

Common Causes:

  • Images without dimensions
  • Ads, embeds, iframes injected dynamically
  • Web fonts causing FOUT/FOIT
  • Animations that trigger layout

How to Fix:

<!-- Always set dimensions -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

<!-- Or use aspect-ratio -->
<img src="photo.jpg" style="aspect-ratio: 16/9; width: 100%;">

<!-- Reserve space for ads -->
<div class="ad-slot" style="min-height: 250px;">
  <!-- Ad loads here -->
</div>

<!-- Prevent font flash -->
<link rel="preload" href="font.woff2" as="font" crossorigin>
<style>
  @font-face {
    font-family: 'MyFont';
    font-display: swap; /* or optional */
    src: url('font.woff2');
  }
</style>

CSS transforms (don't trigger layout):

/* ❌ Causes layout shift */
.element {
  top: 0;
  transition: top 0.3s;
}
.element:hover {
  top: -10px;
}

/* ✅ No layout shift */
.element {
  transform: translateY(0);
  transition: transform 0.3s;
}
.element:hover {
  transform: translateY(-10px);
}

Supporting Web Vitals

TTFB – Time to First Byte

  • Server response time
  • Target: < 800ms
  • Improve with: caching, CDN, database optimization

FCP – First Contentful Paint

  • First pixel rendered
  • Target: < 1.8s
  • Improve with: inline critical CSS, reduce blocking resources

TBT – Total Blocking Time

  • Lab metric (Lighthouse)
  • Sum of blocking time from long tasks
  • Correlates with INP

How to Measure

Field Data (Real Users):

import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

// Send to analytics
function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  navigator.sendBeacon('/analytics', body);
}

onLCP(sendToAnalytics);

Lab Data (Testing):

  • Lighthouse in Chrome DevTools
  • Chrome UX Report (CrUX) - real user data
  • PageSpeed Insights - combines lab + field data
  • WebPageTest - detailed waterfall analysis

Tools:

  • Chrome DevTools → Performance tab
  • Google Search Console → Core Web Vitals report
  • web.dev/measure

One-Line Summary

Core Web Vitals measure loading (LCP), interactivity (INP), and visual stability (CLS) based on real user experience.


8. Caching & Offline

Theory

Caching eliminates network requests entirely - the fastest request is no request. Service Workers enable sophisticated caching strategies and offline experiences.

Service Workers

// sw.js - Cache-first strategy
self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/app.js',
        '/logo.png'
      ]);
    })
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then(response => {
      return response || fetch(e.request);
    })
  );
});

Caching Strategies

1. Cache First

// Try cache, fallback to network
caches.match(request) || fetch(request)

Use for: Static assets, images

2. Network First

// Try network, fallback to cache
fetch(request).catch(() => caches.match(request))

Use for: API calls, dynamic content

3. Stale-While-Revalidate

const cached = await caches.match(request);
const fetching = fetch(request).then(res => {
  cache.put(request, res.clone());
  return res;
});
return cached || fetching;

Use for: Balance freshness & speed

IndexedDB for Large Data

const db = await openDB('my-db', 1, {
  upgrade(db) {
    db.createObjectStore('posts');
  }
});

await db.put('posts', data, 'post-123');
const post = await db.get('posts', 'post-123');

9. Measuring Performance

Theory

"You can't improve what you don't measure." Performance monitoring should be continuous, not one-time.

Tools

Chrome DevTools → Performance Tab

  • Record real interactions
  • See flame charts, long tasks
  • Identify bottlenecks

Lighthouse

  • Automated audits
  • Performance score + suggestions
  • Built into Chrome DevTools

WebPageTest

  • Test from different locations
  • Connection throttling
  • Filmstrip view, waterfall

React Profiler

import { Profiler } from 'react';

<Profiler id="App" onRender={callback}>
  <App />
</Profiler>

function callback(id, phase, actualDuration) {
  console.log(`${id} took ${actualDuration}ms`);
}

What to Look For

Long Tasks (>50ms)

  • Block user interactions
  • Split into smaller chunks

JS Parse/Execute Time

  • Large bundles slow startup
  • Code split aggressively

Layout Shifts

  • Measure CLS
  • Set image dimensions

Network Waterfalls

  • Identify blocking resources
  • Check compression, caching
  • Look for request chains

Performance Budget

{
  "budgets": [{
    "resourceType": "script",
    "budget": 300
  }, {
    "resourceType": "total",
    "budget": 500
  }]
}

Set budgets, fail builds if exceeded.


Web Security

Theory: Defense in Depth

Security is layered. No single measure is perfect, but combining multiple strategies creates robust protection. Always assume user input is malicious and verify everything server-side.


10. XSS (Cross-Site Scripting)

Theory

XSS allows attackers to inject malicious JavaScript into your site, running in the context of your domain with full access to cookies, localStorage, and the DOM.

Attack Vector: Attacker injects malicious JS into your site

Types

1. Stored XSS

  • Saved in database
  • Affects all users who view it
  • Example: Comment systems, user profiles

2. Reflected XSS

  • Via URL/query parameters
  • Affects single user (via phishing link)
  • Example: Search results, error messages

3. DOM XSS

  • Unsafe client-side JS manipulation
  • Never reaches server
  • Example: innerHTML, eval()

Vulnerable Example

// ❌ DANGEROUS
const userInput = "<img src=x onerror='alert(1)'>";
div.innerHTML = userInput; // XSS!

// ❌ DANGEROUS
const search = new URLSearchParams(location.search).get('q');
document.write(search); // XSS!

Prevention

X-Frame-Options Header

X-Frame-Options: DENY
  • DENY: Cannot be framed at all
  • SAMEORIGIN: Only same-origin can frame
  • ALLOW-FROM: Specific origin can frame (deprecated)

Content Security Policy (Modern)

Content-Security-Policy: frame-ancestors 'none';

More flexible than X-Frame-Options.


16. Content Security Policy (CSP)

Theory

CSP is a powerful security layer that restricts what resources can load and execute. It's your best defense against XSS attacks by whitelisting trusted sources.

Basic CSP

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

Directives

Directive Purpose
default-src Fallback for other directives
script-src JavaScript sources
style-src CSS sources
img-src Image sources
connect-src AJAX, WebSocket, fetch
font-src Font sources
object-src <object>, <embed>
frame-ancestors Who can frame this page

Special Values

'self'           # Same origin
'none'           # Block everything
'unsafe-inline'  # Allow inline scripts/styles (avoid!)
'unsafe-eval'    # Allow eval() (avoid!)
'nonce-xyz123'   # Specific inline script with nonce
'strict-dynamic' # Trust scripts loaded by trusted scripts

Nonce-based CSP (Recommended)

<!-- Server generates random nonce -->
<meta http-equiv="Content-Security-Policy"
      content="script-src 'nonce-2726c7f26c'">

<!-- Only scripts with matching nonce execute -->
<script nonce="2726c7f26c">
  console.log('This runs');
</script>

<script>
  console.log('This is blocked');
</script>

Report-Only Mode (Testing)

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Logs violations without blocking - perfect for testing.


17. HTTPS & Man-in-the-Middle

Theory

HTTPS encrypts communication between browser and server, preventing eavesdropping and tampering. It's foundational to modern web security.

Why HTTPS Matters

1. Encryption

  • Prevents packet sniffing
  • Protects sensitive data (passwords, credit cards)

2. Integrity

  • Detects tampering
  • Ensures data arrives unchanged

3. Authentication

  • Verifies server identity
  • Prevents impersonation

4. Modern APIs Require It

  • Service Workers
  • Geolocation
  • Camera/microphone
  • HTTP/2

Enforce HTTPS

HTTP Strict Transport Security (HSTS)

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age: How long to remember (seconds)
  • includeSubDomains: Apply to all subdomains
  • preload: Include in browser's HSTS preload list

Redirect HTTP → HTTPS

// Server-side redirect
app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

Certificate Validation

Browsers validate:

  1. Certificate is signed by trusted CA
  2. Certificate matches domain
  3. Certificate hasn't expired
  4. Certificate hasn't been revoked

18. Secure Cookies

Theory

Cookies are the primary authentication mechanism for web apps. Misconfigured cookies are a major security risk.

Cookie Flags

Set-Cookie:
  sessionId=abc123;
  HttpOnly;
  Secure;
  SameSite=Strict;
  Max-Age=3600;
  Path=/;
  Domain=example.com
Flag Purpose Security Impact
HttpOnly JavaScript can't read Prevents XSS theft
Secure HTTPS only Prevents MITM
SameSite Cross-site restrictions Prevents CSRF
Max-Age Expiration time Limits exposure window
Path URL scope Principle of least privilege
Domain Domain scope Isolate subdomains

SameSite Options

# Strictest - never sent cross-site
SameSite=Strict

# Balanced - sent on top-level navigation
SameSite=Lax

# Permissive - always sent (requires Secure)
SameSite=None; Secure

When to use:

  • Strict: Session cookies
  • Lax: Most use cases (default in modern browsers)
  • None: Cross-site embeds (OAuth, payment widgets)

Secure Cookie Example

// Server-side (Express)
res.cookie('token', jwt, {
  httpOnly: true,      // No JS access
  secure: true,        // HTTPS only
  sameSite: 'strict',  // No CSRF
  maxAge: 900000,      // 15 minutes
  path: '/',
  signed: true         // Tamper detection
});

19. Frontend Security Best Practices

Input Validation

// ❌ Never trust client validation alone
function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// ✅ Validate on backend too
app.post('/register', (req, res) => {
  if (!isValidEmail(req.body.email)) {
    return res.status(400).json({ error: 'Invalid email' });
  }
  // ... proceed
});

Secrets Management

// ❌ NEVER expose secrets in frontend
const API_KEY = 'sk_live_abc123'; // Visible in source!

// ✅ Use env vars at BUILD time only
const PUBLIC_KEY = process.env.NEXT_PUBLIC_STRIPE_KEY;

// ✅ Keep secrets server-side
// server.js
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

API Security

// ❌ Don't expose internal APIs
fetch('/admin/delete-all-users'); // Public endpoint!

// ✅ Verify authorization server-side
app.delete('/admin/users', authorize('admin'), (req, res) => {
  // Check req.user.role
});

// ✅ Rate limiting
const rateLimit = require('express-rate-limit');

app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
}));

Dependency Security

# Check for vulnerabilities
npm audit

# Fix automatically
npm audit fix

# Use Dependabot or Renovate
# Regular updates prevent known exploits

Security Headers Checklist

// helmet.js middleware (Express)
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'nonce-randomvalue'"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  frameguard: {
    action: 'deny'
  },
  noSniff: true,
  xssFilter: true
}));

Common Pitfalls

❌ Don't ✅ Do
Store JWT in localStorage Use HttpOnly cookies or memory
Trust client-side validation Always validate server-side
Expose API keys in code Use environment variables
Allow unlimited requests Implement rate limiting
Use eval() or Function() Parse JSON safely
Disable CORS without auth Require authentication

20. Common Security Interview Questions

Q: Why is JWT in cookies safer than localStorage? A: HttpOnly cookies prevent JavaScript access, protecting against XSS token theft. localStorage is accessible to any script.

Q: Does CORS secure your backend? A: No, CORS is a browser-only restriction. Anyone can make requests from curl, Postman, or server-side code.

Q: Is React safe from XSS by default? A: Yes, React escapes output by default. But dangerouslySetInnerHTML bypasses this protection.

Q: What's the best CSRF defense? A: Combination of SameSite cookies (Strict/Lax) + CSRF tokens for state-changing operations.

Q: How does CSP prevent XSS? A: By whitelisting trusted script sources and blocking inline scripts, CSP prevents injected malicious code from executing.


Web Accessibility (A11y)

Theory: Why Accessibility Matters

15% of the world's population lives with some form of disability. Accessibility isn't just ethical - it's:

  • Legal requirement (ADA, Section 508)
  • SEO benefit (semantic HTML helps crawlers)
  • Better UX for everyone (keyboard nav, captions)

21. WCAG Principles (POUR)

Theory

Web Content Accessibility Guidelines (WCAG) organized around 4 principles. Level AA compliance is the legal standard in most countries.


1) Perceivable

Users must be able to perceive content.

Text Alternatives

<!-- Images -->
<img src="chart.png" alt="Sales increased 20% in Q4 2024">

<!-- Decorative images -->
<img src="divider.png" alt="" role="presentation">

<!-- Icons with text -->
<button>
  <svg aria-hidden="true">...</svg>
  Delete
</button>

Captions & Transcripts

<!-- Video with captions -->
<video controls>
  <source src="video.mp4">
  <track kind="captions" src="captions.vtt" srclang="en" label="English">
</video>

<!-- Audio transcript -->
<audio controls src="podcast.mp3"></audio>
<a href="transcript.txt">Read transcript</a>

Color Contrast

  • Normal text: 4.5:1 minimum
  • Large text (18pt+): 3:1 minimum
  • UI components: 3:1 minimum
/* ❌ Bad - insufficient contrast */
.text {
  color: #999;
  background: #fff; /* 2.8:1 */
}

/* ✅ Good - meets WCAG AA */
.text {
  color: #595959;
  background: #fff; /* 7:1 */
}

Don't Rely on Color Alone

<!-- ❌ Color only -->
<span style="color: red;">Error</span>

<!-- ✅ Color + icon + text -->
<span class="error">
  <svg aria-hidden="true"></svg>
  Error: Invalid email format
</span>

2) Operable

Users must be able to interact with UI.

Keyboard Accessibility

All interactive elements must be keyboard accessible:

  • Tab - move forward
  • Shift + Tab - move backward
  • Enter / Space - activate
  • Esc - close modals/menus
  • Arrow keys - navigate within components
/* ✅ Always show focus indicator */
button:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* ❌ NEVER do this */
*:focus {
  outline: none;
}

No Keyboard Traps

// ✅ Modal focus trap
function trapFocus(modal) {
  const focusable = modal.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );

  const first = focusable[0];
  const last = focusable[focusable.length - 1];

  modal.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    }
  });
}

Skip Links

<!-- Allow keyboard users to skip navigation -->
<a href="#main" class="skip-link">Skip to main content</a>

<nav>...</nav>

<main id="main">...</main>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px;
}

.skip-link:focus {
  top: 0;
}

Avoid Flashing Content

  • No more than 3 flashes per second
  • Can trigger seizures

3) Understandable

Users must understand content and interactions.

Clear Labels

<!-- ✅ Explicit label association -->
<label for="email">Email Address</label>
<input id="email" type="email" required>

<!-- ✅ Implicit label -->
<label>
  Password
  <input type="password" required>
</label>

<!-- ✅ ARIA label when visual label missing -->
<input type="search" aria-label="Search products">

Helper Text

<label for="password">Password</label>
<input
  id="password"
  type="password"
  aria-describedby="password-help"
  required
>
<span id="password-help">
  Must be at least 8 characters with 1 number
</span>

Error Messages

<!-- ✅ Accessible error -->
<label for="email">Email</label>
<input
  id="email"
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
>
<span id="email-error" role="alert">
  Please enter a valid email address
</span>

Predictable Navigation

  • Consistent layout across pages
  • Navigation in same place
  • No unexpected focus changes

4) Robust

Content must work with assistive technologies.

Semantic HTML First

<!-- ✅ Semantic -->
<header>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Title</h1>
    <p>Content...</p>
  </article>
</main>

<footer>
  <p>&copy; 2024</p>
</footer>

Valid HTML

<!-- ❌ Invalid -->
<div>
  <p>Text
</div>

<!-- ✅ Valid -->
<div>
  <p>Text</p>
</div>

ARIA When Needed

<!-- Loading state -->
<button aria-busy="true" aria-live="polite">
  Loading...
</button>

<!-- Live region for dynamic content -->
<div aria-live="polite" aria-atomic="true">
  3 items added to cart
</div>

22. Semantic HTML > ARIA

The Golden Rule

"No ARIA is better than bad ARIA."

If native HTML can do it, don't use ARIA.

Good Examples

<!-- ✅ Native button -->
<button>Click me</button>

<!-- ❌ ARIA button -->
<div role="button" tabindex="0" onclick="handleClick()">Click me</div>

<!-- ✅ Native navigation -->
<nav>
  <a href="/about">About</a>
</nav>

<!-- ❌ ARIA navigation -->
<div role="navigation">
  <span role="link" onclick="navigate('/about')">About</span>
</div>

When ARIA Is Necessary

Custom Widgets

<!-- Tabs (no native HTML) -->
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel1">
    Tab 1
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel2">
    Tab 2
  </button>
</div>

<div id="panel1" role="tabpanel">Content 1</div>
<div id="panel2" role="tabpanel" hidden>Content 2</div>

Dynamic Content

<!-- Alert -->
<div role="alert">Payment successful!</div>

<!-- Status update -->
<div role="status" aria-live="polite">
  5 new messages
</div>

Additional Context

<!-- Expanded state -->
<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
<ul id="menu" hidden>...</ul>

<!-- Current page -->
<nav>
  <a href="/" aria-current="page">Home</a>
  <a href="/about">About</a>
</nav>

Common ARIA Attributes

Attribute Purpose Example
aria-label Accessible name <button aria-label="Close">×</button>
aria-labelledby Reference to label <div aria-labelledby="title">
aria-describedby Additional description <input aria-describedby="help">
aria-hidden Hide from screen readers <svg aria-hidden="true">
aria-live Announce updates <div aria-live="polite">
aria-expanded Collapsible state <button aria-expanded="true">
aria-current Current item <a aria-current="page">
aria-invalid Validation state <input aria-invalid="true">

23. Keyboard Accessibility

Theory

Many users navigate exclusively via keyboard: power users, motor disabilities, screen reader users. Every interactive element must be keyboard accessible.

Keyboard Navigation Patterns

Tab Order

<!-- Natural tab order follows DOM order -->
<button>First</button>
<button>Second</button>
<button>Third</button>

<!-- ❌ Don't manipulate with tabindex > 0 -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>

<!-- ✅ Use tabindex="-1" to exclude from tab order -->
<div tabindex="-1">Not tabbable but focusable</div>

Focus Management

// ✅ Return focus after modal closes
function openModal() {
  previousFocus = document.activeElement;
  modal.show();
  modal.querySelector('button').focus();
}

function closeModal() {
  modal.hide();
  previousFocus.focus(); // Restore focus
}

Keyboard Event Handling

// ✅ Handle both click and keyboard
button.addEventListener('click', handleAction);

button.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    handleAction();
  }
});

// ✅ Or use <button> which does this automatically

Custom Component Example

// Accessible dropdown
function Dropdown({ options }) {
  const [isOpen, setIsOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);

  const handleKeyDown = (e) => {
    switch(e.key) {
      case 'Enter':
      case ' ':
        e.preventDefault();
        setIsOpen(!isOpen);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex((i) => Math.min(i + 1, options.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex((i) => Math.max(i - 1, 0));
        break;
    }
  };

  return (
    <div>
      <button
        aria-expanded={isOpen}
        aria-haspopup="listbox"
        onKeyDown={handleKeyDown}
        onClick={() => setIsOpen(!isOpen)}
      >
        Select option
      </button>

      {isOpen && (
        <ul role="listbox">
          {options.map((opt, i) => (
            <li
              key={opt.id}
              role="option"
              aria-selected={i === activeIndex}
            >
              {opt.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

24. Color & Contrast

Theory

Color blindness affects 8% of men and 0.5% of women. Proper contrast ensures readability for everyone, including users with low vision or in bright sunlight.

Contrast Requirements (WCAG AA)

  • Normal text: 4.5:1
  • Large text (18pt+ or 14pt+ bold): 3:1
  • UI components & graphics: 3:1

Testing Tools

  • Chrome DevTools (Inspect → Accessibility)
  • WebAIM Contrast Checker
  • Stark plugin (Figma/Sketch)

Color Usage

/* ❌ Color alone conveys meaning */
.error {
  color: #ff0000;
}

/* ✅ Color + icon + text */
.error {
  color: #c00;
  font-weight: 600;
}

.error::before {
  content: '⚠️ ';
}
<!-- ❌ Color-only legend -->
<div>
  <span style="color: red;"></span> Error
  <span style="color: green;"></span> Success
</div>

<!-- ✅ Icon + color + label -->
<div>
  <span class="status-error">
    <svg></svg>
    <span>Error</span>
  </span>
  <span class="status-success">
    <svg></svg>
    <span>Success</span>
  </span>
</div>

Focus Indicators

/* ✅ High contrast focus */
:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* ✅ Different focus for dark backgrounds */
.dark-bg :focus-visible {
  outline-color: #fff;
}

/* ❌ Removing outline without alternative */
:focus {
  outline: none; /* DON'T DO THIS */
}

25. Accessible React Patterns

Buttons vs Divs

// ❌ DIV button (inaccessible)
<div onClick={handleSave}>Save</div>

// ✅ Real button
<button onClick={handleSave}>Save</button>

// ✅ Button-styled link for navigation
<a href="/dashboard" className="button">Dashboard</a>

Accessible Modals

function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef();

  useEffect(() => {
    if (isOpen) {
      // Store previous focus
      const previousFocus = document.activeElement;

      // Focus modal
      modalRef.current?.focus();

      // Cleanup: restore focus
      return () => previousFocus?.focus();
    }
  }, [isOpen]);

  useEffect(() => {
    const handleEscape = (e) => {
      if (e.key === 'Escape') onClose();
    };

    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [onClose]);

  if (!isOpen) return null;

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabIndex={-1}
    >
      <h2 id="modal-title">Modal Title</h2>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Accessible Forms

function LoginForm() {
  const [errors, setErrors] = useState({});

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          aria-required="true"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? "email-error" : undefined}
        />
        {errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          aria-required="true"
          aria-describedby="password-help"
        />
        <span id="password-help">
          At least 8 characters
        </span>
      </div>

      <button type="submit">Log In</button>
    </form>
  );
}

Accessible Images

// ✅ Informative image
<img src="graph.png" alt="Revenue growth chart showing 25% increase in Q4" />

// ✅ Decorative image
<img src="divider.png" alt="" role="presentation" />

// ✅ Icon with text
<button>
  <svg aria-hidden="true">
    <TrashIcon />
  </svg>
  Delete
</button>

// ✅ Icon-only button
<button aria-label="Delete item">
  <svg aria-hidden="true">
    <TrashIcon />
  </svg>
</button>

Live Regions

function NotificationSystem() {
  const [message, setMessage] = useState('');

  return (
    <div
      role="status"
      aria-live="polite"
      aria-atomic="true"
    >
      {message}
    </div>
  );
}

// Usage
function handleSave() {
  save();
  setMessage('Changes saved successfully');

  setTimeout(() => setMessage(''), 5000);
}

26. Forms Accessibility

Theory

Forms are the primary way users interact with web apps. Accessible forms are critical for usability.

Label Association

<!-- ✅ Explicit association (preferred) -->
<label for="username">Username</label>
<input id="username" type="text">

<!-- ✅ Implicit association -->
<label>
  Username
  <input type="text">
</label>

<!-- ❌ No association -->
<label>Username</label>
<input type="text">

Required Fields

<!-- ✅ Visual + programmatic -->
<label for="email">
  Email <span aria-label="required">*</span>
</label>
<input id="email" type="email" required aria-required="true">

<!-- ✅ Alternative: text indication -->
<label for="email">Email (required)</label>
<input id="email" type="email" required>

Error Handling

function SignupForm() {
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});

  const validate = (name, value) => {
    // Validation logic
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      {/* Error summary for screen readers */}
      {Object.keys(errors).length > 0 && (
        <div role="alert" aria-live="assertive">
          <h2>Please fix the following errors:</h2>
          <ul>
            {Object.entries(errors).map(([field, message]) => (
              <li key={field}>
                <a href={`#${field}`}>{message}</a>
              </li>
            ))}
          </ul>
        </div>
      )}

      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? "email-error" : "email-help"}
          onBlur={() => setTouched({ ...touched, email: true })}
        />
        <span id="email-help">We'll never share your email</span>
        {errors.email && touched.email && (
          <span id="email-error" role="alert" className="error">
            {errors.email}
          </span>
        )}
      </div>

      <button type="submit">Sign Up</button>
    </form>
  );
}

Fieldsets & Legends

<!-- ✅ Group related fields -->
<fieldset>
  <legend>Shipping Address</legend>

  <label for="street">Street</label>
  <input id="street" type="text">

  <label for="city">City</label>
  <input id="city" type="text">
</fieldset>

<fieldset>
  <legend>Payment Method</legend>

  <label>
    <input type="radio" name="payment" value="card">
    Credit Card
  </label>

  <label>
    <input type="radio" name="payment" value="paypal">
    PayPal
  </label>
</fieldset>

Autocomplete Attributes

<!-- ✅ Help autofill and password managers -->
<input
  type="email"
  autocomplete="email"
  id="email"
>

<input
  type="password"
  autocomplete="current-password"
  id="current-password"
>

<input
  type="password"
  autocomplete="new-password"
  id="new-password"
>

<input
  type="text"
  autocomplete="address-line1"
>

27. Testing Tools

Automated Testing

Lighthouse (Chrome DevTools)

# Command line
lighthouse https://example.com --only-categories=accessibility

axe DevTools

  • Browser extension
  • Catches 30-50% of issues
  • Detailed remediation guidance

Pa11y

npm install -g pa11y

pa11y https://example.com

Jest + jest-axe

import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';

expect.extend(toHaveNoViolations);

test('should have no accessibility violations', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

ESLint Plugin

{
  "extends": ["plugin:jsx-a11y/recommended"],
  "plugins": ["jsx-a11y"]
}

Manual Testing

Keyboard Navigation

  1. Unplug your mouse
  2. Navigate entire site with Tab, Enter, Space, Esc, arrows
  3. Check:
    • Can you reach everything?
    • Is focus visible?
    • Is order logical?
    • Any keyboard traps?

Screen Reader Testing

Windows: NVDA (free)

# Download from https://www.nvaccess.org/
# Ctrl + Alt + N to start

macOS: VoiceOver (built-in)

# Cmd + F5 to toggle
# VO + A to read page
# VO + Right Arrow to navigate

Common Screen Reader Commands:

Action NVDA VoiceOver
Start/Stop Ctrl + Alt + N Cmd + F5
Read all Insert + ↓ VO + A
Next item VO + →
Next heading H VO + Cmd + H
Next link K VO + Cmd + L
Forms mode Insert + Space Auto

Color Contrast Checkers

  • Chrome DevTools (Inspect element → Accessibility pane)
  • WebAIM Contrast Checker
  • Stark plugin (Figma)
  • Colour Contrast Analyser (desktop app)

Testing Checklist

## Accessibility Testing Checklist

### Keyboard
- [ ] All interactive elements reachable via Tab
- [ ] Visible focus indicator on all elements
- [ ] Logical tab order (follows visual layout)
- [ ] No keyboard traps
- [ ] Esc closes modals/dropdowns
- [ ] Skip navigation link works

### Screen Reader
- [ ] Page has meaningful title
- [ ] Headings create logical outline
- [ ] All images have alt text (or alt="" for decorative)
- [ ] Form labels properly associated
- [ ] Error messages announced
- [ ] Dynamic content updates announced
- [ ] ARIA attributes used correctly

### Visual
- [ ] Text contrast meets WCAG AA (4.5:1)
- [ ] Focus indicators meet contrast (3:1)
- [ ] Color not sole means of conveying info
- [ ] Text resizes to 200% without breaking
- [ ] No information lost in mobile view

### Content
- [ ] Link text descriptive ("Read more about accessibility" not "Click here")
- [ ] Headings in correct order (h1 → h2 → h3)
- [ ] Language attribute set (<html lang="en">)
- [ ] Page has unique, descriptive title

### Forms
- [ ] All inputs have labels
- [ ] Required fields marked (visually + programmatically)
- [ ] Error messages clear and associated with fields
- [ ] Success messages announced
- [ ] Autocomplete attributes used where appropriate

### Media
- [ ] Videos have captions
- [ ] Audio has transcripts
- [ ] No autoplay (or easy to pause)
- [ ] No content flashes more than 3x per second

28. Interview One-Liners

Performance

Q: What's the biggest performance killer? A: Too much JavaScript. It must be downloaded, parsed, compiled, and executed - all blocking the main thread.

Q: What's the fastest performance win? A: Code splitting + image optimization. Lazy load what you don't need immediately.

Q: Why use transform over top/left? A: Transform only triggers compositing, while top/left triggers full layout recalculation (reflow).

Q: Why use React.memo? A: Prevents unnecessary re-renders by memoizing the component, only re-rendering when props change.

Q: Why use a CDN? A: Lower latency (geographically closer to users) + edge caching + reduces load on origin server.

Q: What are Core Web Vitals? A: Google's user-centric performance metrics: LCP (loading), INP (interactivity), CLS (visual stability).

Q: Difference between defer and async? A: Both download in parallel, but defer executes after HTML parsing while async executes immediately when downloaded.

Q: What's the Critical Rendering Path? A: HTML→DOM, CSS→CSSOM, DOM+CSSOM→Render Tree, Layout, Paint, Composite.

Q: How to reduce bundle size? A: Tree-shaking, code splitting, dynamic imports, remove unused dependencies, compression (Brotli/Gzip).

Q: What's lazy loading? A: Deferring load of non-critical resources until they're needed (images, routes, components).


Security

Q: What is XSS? A: Cross-Site Scripting - attacker injects malicious JavaScript into your site, executing in users' browsers.

Q: What is CSRF? A: Cross-Site Request Forgery - attacker tricks users into making unwanted authenticated requests.

Q: Why is JWT in cookies safer than localStorage? A: HttpOnly cookies prevent JavaScript access, protecting against XSS token theft.

Q: Does CORS secure your backend? A: No, CORS is a browser-only restriction. Server-to-server requests bypass CORS entirely.

Q: Is React safe from XSS by default? A: Yes, React escapes output automatically. But dangerouslySetInnerHTML bypasses this protection.

Q: Best CSRF defense? A: SameSite cookies (Strict/Lax) + CSRF tokens for state-changing operations.

Q: What does CSP do? A: Content Security Policy whitelists trusted sources for scripts, styles, and other resources, blocking injected malicious code.

Q: Why HTTPS? A: Encryption (prevents eavesdropping), integrity (prevents tampering), authentication (verifies server identity).

Q: What are secure cookie flags? A: HttpOnly (no JS access), Secure (HTTPS only), SameSite (CSRF protection).

Q: What's the difference between authentication and authorization? A: Authentication is "who are you?" (login), Authorization is "what can you access?" (permissions).


Accessibility

Q: What is accessibility? A: Making web content usable by everyone, including people with disabilities (visual, auditory, motor, cognitive).

Q: What are the WCAG principles? A: POUR - Perceivable, Operable, Understandable, Robust.

Q: Semantic HTML vs ARIA? A: Always use semantic HTML first. ARIA is only for custom widgets where native HTML doesn't exist.

Q: Why is keyboard accessibility important? A: Many users can't use a mouse: power users, motor disabilities, screen reader users.

Q: What's the golden rule of ARIA? A: "No ARIA is better than bad ARIA." If native HTML can do it, don't use ARIA.

Q: What color contrast ratio is required? A: WCAG AA requires 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt+ bold).

Q: Why not remove focus outlines? A: Keyboard users need visible focus indicators to know where they are on the page.

Q: What is alt text for? A: Describes images for screen reader users and when images fail to load. Use alt="" for decorative images.

Q: What's aria-label vs aria-labelledby? A: aria-label provides the label directly. aria-labelledby references another element's ID.

Q: How to make a modal accessible? A: Trap focus inside modal, restore focus on close, aria-modal="true", close on Esc, focus first element on open.


29. Quick Reference: Mental Models

Performance Mental Model

Network (biggest wins)
  ↓
JavaScript (parse/execute cost)
  ↓
React (render optimization)
  ↓
CSS (layout triggers)
  ↓
Images (lazy loading)
  ↓
Caching (offline)

One-sentence summary: Minimize network requests, reduce JavaScript, optimize renders, avoid layout thrashing.


Security Mental Model

XSS = Execute malicious JS
CSRF = Abuse trust
CORS = Browser-only rule
CSP = Whitelist trusted sources

Defense layers:

  1. Input validation (client + server)
  2. Output escaping
  3. CSP headers
  4. Secure cookies (HttpOnly, Secure, SameSite)
  5. HTTPS everywhere

Accessibility Mental Model

Perceivable → Can users see/hear content?
  ↓
Operable → Can users interact?
  ↓
Understandable → Can users comprehend?
  ↓
Robust → Does it work with assistive tech?

One-sentence summary: Semantic HTML first, keyboard accessible, sufficient contrast, screen reader friendly.


30. Real-World Interview Questions

Performance

Q: How would you optimize a slow React app?

A: Systematic approach:

  1. Profile first - React DevTools Profiler, identify slow components
  2. Prevent re-renders - React.memo, useCallback, useMemo
  3. Code split - Lazy load routes and heavy components
  4. Virtualize lists - react-window for long lists
  5. Optimize images - WebP, lazy loading, responsive images
  6. Reduce bundle - Tree-shake, remove unused deps
  7. Measure Core Web Vitals - Focus on LCP, INP, CLS

Q: Walk me through optimizing a page with LCP of 5 seconds.

A:

  1. Identify LCP element - Use Lighthouse or Chrome DevTools
  2. If it's an image:
    • Optimize format (WebP/AVIF)
    • Use CDN
    • Preload: <link rel="preload" as="image" href="hero.jpg">
    • Add fetchpriority="high"
  3. If it's text:
    • Inline critical CSS
    • Preconnect to font sources
    • Use font-display: swap
  4. Reduce TTFB:
    • Server-side rendering
    • Edge caching
    • Database optimization
  5. Remove render-blocking resources:
    • Defer non-critical JS
    • Async CSS loading

Q: Explain how you'd implement infinite scroll with good performance.

A:

import { useEffect, useRef, useState } from 'react';
import { FixedSizeList } from 'react-window';

function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const observer = useRef();

  // Intersection Observer for detecting scroll bottom
  const lastItemRef = useCallback(node => {
    if (loading) return;
    if (observer.current) observer.current.disconnect();

    observer.current = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        setPage(prev => prev + 1);
      }
    });

    if (node) observer.current.observe(node);
  }, [loading]);

  // Fetch more data
  useEffect(() => {
    setLoading(true);
    fetchItems(page).then(newItems => {
      setItems(prev => [...prev, ...newItems]);
      setLoading(false);
    });
  }, [page]);

  // Virtualize for performance
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {({ index, style }) => (
        <div
          style={style}
          ref={index === items.length - 1 ? lastItemRef : null}
        >
          {items[index].title}
        </div>
      )}
    </FixedSizeList>
  );
}

Key optimizations:

  • Virtualization (only render visible items)
  • Intersection Observer (native, performant)
  • Avoid layout thrashing
  • Debounce scroll events if needed

Security

Q: How would you secure a user authentication system?

A: Multi-layered approach:

  1. Password Security:

    • Hash with bcrypt/Argon2 (never store plaintext)
    • Enforce strong password requirements
    • Rate limit login attempts
  2. Token Management:

    • Short-lived access tokens (15min) in memory
    • Long-lived refresh tokens in HttpOnly cookies
    • Rotate refresh tokens on use
  3. Transport Security:

    • HTTPS everywhere
    • HSTS headers
    • Secure cookie flags (HttpOnly, Secure, SameSite)
  4. CSRF Protection:

    • SameSite=Strict cookies
    • CSRF tokens for state-changing operations
  5. XSS Prevention:

    • CSP headers
    • Escape output (React does by default)
    • Sanitize any dangerouslySetInnerHTML
// Example implementation
app.post('/login', rateLimiter, async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  if (!user || !await bcrypt.compare(req.body.password, user.passwordHash)) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const accessToken = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '15m' });
  const refreshToken = jwt.sign({ userId: user.id }, REFRESH_SECRET, { expiresIn: '7d' });

  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000
  });

  res.json({ accessToken });
});

Q: A user reports they can see other users' data. How do you investigate?

A: Systematic approach:

  1. Immediate action:

    • Isolate affected endpoint
    • Review recent deployments
    • Check logs for unauthorized access
  2. Common causes:

    • Missing authorization checks
    • SQL injection
    • IDOR (Insecure Direct Object Reference)
    • Broken access control
  3. Investigation:

// ❌ Vulnerable code
app.get('/api/user/:id', (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user); // No authorization check!
});

// ✅ Fixed code
app.get('/api/user/:id', authenticate, async (req, res) => {
  const user = await User.findById(req.params.id);

  // Verify requesting user owns this data
  if (user.id !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  res.json(user);
});
  1. Prevention:
    • Always verify authorization server-side
    • Principle of least privilege
    • Audit logs
    • Regular security testing

Accessibility

Q: How would you make a complex data table accessible?

A:

<table>
  <caption>
    Quarterly Sales Report
    <details>
      <summary>Table description</summary>
      Shows sales by region and product for Q4 2024
    </details>
  </caption>

  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">Product</th>
      <th scope="col">Revenue</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>Widget A</td>
      <td>$125,000</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <th scope="row" colspan="2">Total</th>
      <td>$500,000</td>
    </tr>
  </tfoot>
</table>

Key features:

  • <caption> describes table purpose
  • scope="col" for column headers
  • scope="row" for row headers
  • Sortable columns announce state
  • Keyboard navigation (arrow keys)
  • For complex tables: headers attribute

For large tables:

  • Pagination or virtual scrolling
  • Filter/search functionality
  • Sticky headers
  • Export to CSV option

Q: User complains your site doesn't work with keyboard. How do you debug?

A: Systematic debugging:

  1. Reproduce issue:

    • Unplug mouse
    • Navigate with Tab only
    • Document what fails
  2. Common issues:

// ❌ Problem 1: Div buttons
<div onClick={handleClick}>Click me</div>

// ✅ Fix: Use real button
<button onClick={handleClick}>Click me</button>

// ❌ Problem 2: No visible focus
button:focus { outline: none; }

// ✅ Fix: Clear focus indicator
button:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

// ❌ Problem 3: Modal keyboard trap
function Modal({ isOpen, onClose }) {
  return <div>{children}</div>; // Focus escapes modal
}

// ✅ Fix: Trap focus
function Modal({ isOpen, onClose }) {
  const modalRef = useRef();

  useEffect(() => {
    if (!isOpen) return;

    const focusableElements = modalRef.current.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );

    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    const trapFocus = (e) => {
      if (e.key !== 'Tab') return;

      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    };

    modalRef.current.addEventListener('keydown', trapFocus);
    firstElement.focus();

    return () => {
      modalRef.current?.removeEventListener('keydown', trapFocus);
    };
  }, [isOpen]);

  return (
    <div ref={modalRef} role="dialog" aria-modal="true">
      {children}
    </div>
  );
}
  1. Testing checklist:
    • All interactive elements reachable
    • Visible focus indicator
    • Logical tab order
    • Escape closes overlays
    • No keyboard traps
    • Custom widgets have proper keyboard handling

31. Bonus: Performance Budget Example

{
  "budget": [
    {
      "resourceType": "script",
      "budget": 300
    },
    {
      "resourceType": "stylesheet",
      "budget": 50
    },
    {
      "resourceType": "image",
      "budget": 400
    },
    {
      "resourceType": "total",
      "budget": 800
    }
  ],
  "metrics": [
    {
      "metric": "interactive",
      "budget": 3000
    },
    {
      "metric": "first-contentful-paint",
      "budget": 1500
    }
  ]
}

Enforce in CI/CD:

# Lighthouse CI
lighthouse-ci --budget-path=budget.json --fail-on-violation

32. Additional Resources

Performance

Security

Accessibility


Summary: The 3 Pillars

⚡ Performance

Goal: Fast, smooth user experience

  • Minimize network (CDN, compression, caching)
  • Optimize JavaScript (code splitting, defer)
  • Efficient React (memo, virtualization)
  • Measure Core Web Vitals (LCP, INP, CLS)

🔒 Security

Goal: Protect users and data

  • Prevent XSS (escape output, CSP)
  • Prevent CSRF (SameSite cookies, tokens)
  • Secure auth (HTTPS, HttpOnly cookies, JWT rotation)
  • Defense in depth (multiple layers)

♿ Accessibility

Goal: Usable by everyone

  • Semantic HTML first
  • Keyboard accessible
  • Screen reader friendly
  • Sufficient contrast
  • POUR principles (Perceivable, Operable, Understandable, Robust)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment