Code Library

Production-Ready Code Snippets

Copy-paste solutions for every Core Web Vital issue. Each snippet is tested and ready to ship.

23 snippets
LCP

Preload Above-the-Fold Images

Preloading your hero/LCP image drastically reduces discovery time, typically improving LCP by 500ms–1.5s.

index.html
<!-- Add in <head> before any stylesheets -->
<link rel="preload"
  as="image"
  href="/hero-image.webp"
  fetchpriority="high"
  imagesrcset="/hero-480.webp 480w, /hero-800.webp 800w, /hero-1200.webp 1200w"
  imagesizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
/>

<!-- The image tag itself -->
<img
  src="/hero-image.webp"
  alt="Hero"
  width="1200"
  height="600"
  fetchpriority="high"
  decoding="async"
/>
LCP

Reduce Server Response Time (TTFB)

Use HTTP caching headers and Edge delivery to reduce time to first byte, which feeds directly into LCP.

next.config.js
"color:#6a737d;font-style:italic">// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          "color:#6a737d;font-style:italic">// Aggressive cache for static assets
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        source: '/',
        headers: [
          "color:#6a737d;font-style:italic">// Stale-while-revalidate for HTML
          {
            key: 'Cache-Control',
            value: 's-maxage=60, stale-while-revalidate=600',
          },
        ],
      },
    ]
  },
}
LCP

Inline Critical CSS

Inline above-the-fold styles directly in <head> to eliminate render-blocking CSS round trips. Defer the full stylesheet.

index.html
<head>
  <!-- Inline critical CSS for above-the-fold content -->
  <style>
    "color:#6a737d;font-style:italic">/* Only styles needed for initial viewport */
    body { margin: 0; font-family: system-ui, sans-serif; }
    .hero { min-height: 100vh; display: flex;
            align-items: center; justify-content: center; }
    .hero h1 { font-size: clamp(2rem, 5vw, 4rem); }
  </style>

  <!-- Defer full stylesheet — loads asynchronously -->
  <link rel="preload" href="/styles.css"
    as="style" onload="this.rel='stylesheet'" />
  <noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>
LCP

Responsive Hero with <picture>

Use the <picture> element to serve the optimal image format and size for each device, dramatically reducing LCP on mobile.

hero.html
<picture>
  <!-- AVIF for browsers that support it(smallest) -->
  <source type="image/avif"
    srcset="/hero-480.avif 480w,
            /hero-800.avif 800w,
            /hero-1200.avif 1200w"
    sizes="100vw" />

  <!-- WebP fallback -->
  <source type="image/webp"
    srcset="/hero-480.webp 480w,
            /hero-800.webp 800w,
            /hero-1200.webp 1200w"
    sizes="100vw" />

  <!-- JPEG fallback for old browsers -->
  <img src="/hero-1200.jpg"
    alt="Hero banner"
    width="1200" height="600"
    fetchpriority="high"
    decoding="async"
    style="width:100%; height:auto;" />
</picture>
LCP

Preload Critical Fonts

Preloading your primary font eliminates the flash of invisible text (FOIT) and reduces LCP when text is the largest element.

index.html
<head>
  <!-- Preload the critical font file -->
  <link rel="preload"
    href="/fonts/Inter-Variable.woff2"
    as="font"
    type="font/woff2"
    crossorigin="anonymous" />

  <style>
    @font-face {
      font-family: 'Inter';
      src: url('/fonts/Inter-Variable.woff2') format('woff2');
      font-display: swap;   "color:#6a737d;font-style:italic">/* or 'optional' for zero CLS */
      font-weight: 100 900;
      unicode-range: U+0000-00FF; "color:#6a737d;font-style:italic">/* Latin subset */
    }
  </style>
</head>
CLS

Always Set Image Width & Height

The browser reserves space before images load, preventing layout shifts. Use aspect-ratio as the modern CSS approach.

styles.css
"color:#6a737d;font-style:italic">/* Modern CSS approach — set aspect ratio on containers */
.image-container {
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
}

.image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

"color:#6a737d;font-style:italic">/* Or use width/height attributes directly on <img> */
"color:#6a737d;font-style:italic">/* <img src="photo.jpg" width="800" height="450" alt="..."> */

"color:#6a737d;font-style:italic">/* For dynamic iframes or embeds */
.embed-container {
  position: relative;
  padding-bottom: 56.25%; "color:#6a737d;font-style:italic">/* 16:9 */
  height: 0;
}

.embed-container iframe {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}
CLS

Prevent Font-Swap Layout Shifts

Use the size-adjust and ascent-override CSS descriptors to make fallback fonts match your web font dimensions.

fonts.css
"color:#6a737d;font-style:italic">/* Define a size-adjusted fallback to match your custom font */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
  size-adjust: 107%;
}

"color:#6a737d;font-style:italic">/* Use the fallback in your font stack */
body {
  font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}

"color:#6a737d;font-style:italic">/* In Next.js — use the built-in font optimizer */
"color:#6a737d;font-style:italic">/* import { Inter } from 'next/font/google'
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',      "color:#6a737d;font-style:italic">// 'optional' for zero CLS
  preload: true,
})  */
CLS

Reserve Space for Dynamic Content

Ads, banners, and late-loading content cause massive CLS. Always reserve space with min-height or skeleton placeholders.

layout.css
"color:#6a737d;font-style:italic">/* Reserve space for ad slots */
.ad-slot {
  min-height: 250px;  "color:#6a737d;font-style:italic">/* Standard ad height */
  min-width: 300px;
  background: var(--skeleton-bg);
  border-radius: 4px;
  container-type: inline-size;
}

"color:#6a737d;font-style:italic">/* Skeleton loading state */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-card) 25%,
    var(--border) 50%,
    var(--bg-card) 75%
  );
  background-size: 200% 100%;
  animation: skeleton-wave 1.5s infinite;
  border-radius: 4px;
}

@keyframes skeleton-wave {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

"color:#6a737d;font-style:italic">/* Prevent banner/notification from pushing content */
.top-banner {
  height: 44px;           "color:#6a737d;font-style:italic">/* Fixed height — never auto */
  overflow: hidden;
  position: sticky;
  top: 0;
  z-index: 50;
}
CLS

Skeleton Loaders for Async Content

Use skeleton UI to maintain layout stability while data loads. This prevents CLS from empty → populated state transitions.

Skeleton.tsx
"color:#6a737d;font-style:italic">// Reusable Skeleton component
function Skeleton({ width = '100%', height = 20, borderRadius = 4 }) {
  return (
    <div style={{
      width, height, borderRadius,
      background: 'linear-gradient(90deg, #1a1a2e 25%, #2a2a3d 50%, #1a1a2e 75%)',
      backgroundSize: '200% 100%',
      animation: 'skeleton-wave 1.5s infinite',
    }} />
  )
}

"color:#6a737d;font-style:italic">// Usage: Product card with skeleton state
function ProductCard({ product, loading }) {
  if (loading) {
    return (
      <div style={{ padding: '1rem' }}>
        <Skeleton height={200} borderRadius={8} />
        <Skeleton width="60%" height={18} style={{ marginTop: 12 }} />
        <Skeleton width="40%" height={14} style={{ marginTop: 8 }} />
      </div>
    )
  }

  return (
    <div style={{ padding: '1rem' }}>
      <img src={product.image} width={300} height={200} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.price}</p>
    </div>
  )
}
CLS

Use Transform-Only Animations

Animations using top/left/width/height trigger layout shifts. Use transform and opacity instead — they run on the compositor thread.

animations.css
"color:#6a737d;font-style:italic">/* ✗ BAD — causes layout shifts */
.slide-in-bad {
  animation: slideInBad 0.3s ease;
}
@keyframes slideInBad {
  from { left: -100%; }
  to   { left: 0; }
}

"color:#6a737d;font-style:italic">/* ✓ GOOD — no layout shift, GPU-accelerated */
.slide-in-good {
  animation: slideInGood 0.3s ease;
}
@keyframes slideInGood {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}

"color:#6a737d;font-style:italic">/* Hint browser to promote element to own layer */
.will-animate {
  will-change: transform, opacity;
  "color:#6a737d;font-style:italic">/* Remove will-change after animation completes
     to free GPU memory */
}
INP

Debounce Expensive Event Handlers

Long event handlers block the main thread and hurt INP. Debounce + schedule non-critical work with scheduler.postTask.

utils.js
"color:#6a737d;font-style:italic">// Debounce utility
function debounce(fn, wait = 200) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), wait)
  }
}

"color:#6a737d;font-style:italic">// Throttle utility(for scroll/resize)
function throttle(fn, limit = 100) {
  let inThrottle
  return (...args) => {
    if (!inThrottle) {
      fn(...args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}

"color:#6a737d;font-style:italic">// Schedule non-urgent work away from user interaction
function scheduleIdleWork(callback) {
  if ('scheduler' in window && 'postTask' in scheduler) {
    return scheduler.postTask(callback, { priority: 'background' })
  }
  "color:#6a737d;font-style:italic">// Fallback
  return new Promise(resolve => {
    requestIdleCallback(() => resolve(callback()))
  })
}

"color:#6a737d;font-style:italic">// Usage
const input = document.querySelector('#search')
input.addEventListener('input', debounce(async (e) => {
  const results = await fetchSearchResults(e.target.value)
  scheduleIdleWork(() => renderResults(results))
}, 250))
INP

Break Up Long Tasks with yield()

Long JavaScript tasks (>50ms) block user interaction. Yield to the browser periodically to keep INP under 200ms.

tasks.js
"color:#6a737d;font-style:italic">// Yield to main thread between work chunks
function yieldToMain() {
  return new Promise(resolve => setTimeout(resolve, 0))
}

"color:#6a737d;font-style:italic">// Process a large array in chunks
async function processLargeArray(items) {
  const CHUNK_SIZE = 50
  const results = []

  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE)

    "color:#6a737d;font-style:italic">// Process chunk synchronously
    for (const item of chunk) {
      results.push(expensiveOperation(item))
    }

    "color:#6a737d;font-style:italic">// Yield to browser after each chunk
    "color:#6a737d;font-style:italic">// This allows interaction events to be processed
    if (i + CHUNK_SIZE < items.length) {
      await yieldToMain()
    }
  }

  return results
}

"color:#6a737d;font-style:italic">// Modern scheduler API(Chrome 115+)
async function processWithScheduler(items) {
  for (const item of items) {
    await scheduler.yield()  "color:#6a737d;font-style:italic">// Yields if a user interaction is pending
    processItem(item)
  }
}
INP

Offload to Web Workers

Move CPU-intensive work off the main thread entirely. Web Workers run in a separate thread and never block user interactions.

worker-setup.js
"color:#6a737d;font-style:italic">// heavy-worker.js — runs in background thread
self.onmessage = function(e) {
  const { data, operation } = e.data

  let result
  switch(operation) {
    case 'sort':
      result = data.sort((a, b) => a.score - b.score)
      break
    case 'filter':
      result = data.filter(item => item.active)
      break
    case 'transform':
      result = data.map(item => ({
        ...item,
        computed: expensiveCalculation(item),
      }))
      break
  }

  self.postMessage({ result })
}

"color:#6a737d;font-style:italic">// main.js — stays responsive
const worker = new Worker('/heavy-worker.js')

function processDataAsync(data, operation) {
  return new Promise((resolve) => {
    worker.onmessage = (e) => resolve(e.data.result)
    worker.postMessage({ data, operation })
  })
}

"color:#6a737d;font-style:italic">// Usage: main thread stays free for interactions
button.addEventListener('click', async () => {
  button.textContent = 'Processing...'
  const sorted = await processDataAsync(largeDataset, 'sort')
  renderTable(sorted)
  button.textContent = 'Done!'
})
INP

Event Delegation Pattern

Instead of attaching listeners to every list item, delegate to a parent. Reduces memory usage and speeds up initial paint.

delegation.js
"color:#6a737d;font-style:italic">// ✗ BAD — one listener per item(slow with 1000+ items)
document.querySelectorAll('.list-item').forEach(item => {
  item.addEventListener('click', handleClick)
})

"color:#6a737d;font-style:italic">// ✓ GOOD — one listener on parent, delegate to children
document.querySelector('.list-container')
  .addEventListener('click', (e) => {
    const item = e.target.closest('[data-item-id]')
    if (!item) return

    const id = item.dataset.itemId
    handleItemClick(id)
  })

"color:#6a737d;font-style:italic">// Works for dynamically added items too!
"color:#6a737d;font-style:italic">// No need to re-attach listeners when list updates.

"color:#6a737d;font-style:italic">// React equivalent — single handler on parent
function ItemList({ items, onSelect }) {
  return (
    <ul onClick={(e) => {
      const id = e.target.closest('[data-id]')?.dataset.id
      if (id) onSelect(id)
    }}>
      {items.map(item => (
        <li key={item.id} data-id={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
INP

Batch DOM Updates with rAF

Interleaving DOM reads and writes causes layout thrashing. Batch all reads first, then writes inside requestAnimationFrame.

dom-batching.js
"color:#6a737d;font-style:italic">// ✗ BAD — layout thrashing(read-write-read-write)
elements.forEach(el => {
  const height = el.offsetHeight    "color:#6a737d;font-style:italic">// FORCED LAYOUT(read)
  el.style.height = height * 2 + 'px'  "color:#6a737d;font-style:italic">// Write
  "color:#6a737d;font-style:italic">// Next iteration forces layout again!
})

"color:#6a737d;font-style:italic">// ✓ GOOD — batch reads, then batch writes
const heights = elements.map(el => el.offsetHeight)  "color:#6a737d;font-style:italic">// Batch READ

requestAnimationFrame(() => {
  elements.forEach((el, i) => {
    el.style.height = heights[i] * 2 + 'px'  "color:#6a737d;font-style:italic">// Batch WRITE
  })
})

"color:#6a737d;font-style:italic">// For complex updates — use a read/write queue
const domScheduler = {
  reads: [],
  writes: [],
  schedule() {
    "color:#6a737d;font-style:italic">// Execute all reads first
    this.reads.forEach(fn => fn())
    "color:#6a737d;font-style:italic">// Then all writes in a single frame
    requestAnimationFrame(() => {
      this.writes.forEach(fn => fn())
      this.reads = []
      this.writes = []
    })
  }
}
Lazy Loading

Native Lazy Loading + Intersection Observer

Defer off-screen images and components using the native loading="lazy" attribute combined with Intersection Observer for JS-heavy components.

lazyLoad.js
"color:#6a737d;font-style:italic">// 1. Native lazy loading(browser support: 92%+)
"color:#6a737d;font-style:italic">// <img src="photo.jpg" loading="lazy" alt="..." width="800" height="600">

"color:#6a737d;font-style:italic">// 2. Intersection Observer for JS components
function lazyLoadComponent(selector, importFn) {
  const elements = document.querySelectorAll(selector)
  if (!elements.length) return

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(async (entry) => {
      if (entry.isIntersecting) {
        const module = await importFn()
        module.init(entry.target)
        observer.unobserve(entry.target)
      }
    })
  }, {
    rootMargin: '200px',  "color:#6a737d;font-style:italic">// Start loading 200px before visible
    threshold: 0,
  })

  elements.forEach(el => observer.observe(el))
}

"color:#6a737d;font-style:italic">// Usage
lazyLoadComponent('[data-map]', () => import('./map.js'))
lazyLoadComponent('[data-chart]', () => import('./chart.js'))

"color:#6a737d;font-style:italic">// 3. React lazy + Suspense
"color:#6a737d;font-style:italic">// const HeavyChart = React.lazy(() => import('./HeavyChart'))
"color:#6a737d;font-style:italic">// <Suspense fallback={<Skeleton />}><HeavyChart /></Suspense>
Lazy Loading

Route-Level Code Splitting

Split your app at the route level so users only download JS for the page they visit. This is automatic in Next.js, but here is how to do it in React Router.

routes.tsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

"color:#6a737d;font-style:italic">// Lazy-load each route — only downloads when user navigates
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
const HeavyReport = lazy(() =>
  import("color:#6a737d;font-style:italic">/* webpackChunkName: "report" */ './pages/HeavyReport')
)

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageSkeleton />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/report" element={<HeavyReport />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

"color:#6a737d;font-style:italic">// Next.js does this automatically for every file in /app
"color:#6a737d;font-style:italic">// Each page.tsx is its own code-split chunk
Lazy Loading

Lazy <picture> with Fallback

Combine lazy loading with the <picture> element for format negotiation. Include a low-quality placeholder for the best perceived performance.

lazy-picture.html
<!-- Lazy-loaded picture with LQIP(Low Quality Image Placeholder) -->
<div class="image-wrapper" style="aspect-ratio: 16/9; background: #1a1a2e;">
  <picture>
    <source type="image/avif"
      srcset="/photos/landscape-sm.avif 640w,
             /photos/landscape-md.avif 1024w,
             /photos/landscape-lg.avif 1920w"
      sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px" />

    <source type="image/webp"
      srcset="/photos/landscape-sm.webp 640w,
             /photos/landscape-md.webp 1024w,
             /photos/landscape-lg.webp 1920w"
      sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px" />

    <img
      src="/photos/landscape-md.jpg"
      alt="Mountain landscape"
      width="1200" height="675"
      loading="lazy"
      decoding="async"
      style="width: 100%; height: auto; object-fit: cover;"
    />
  </picture>
</div>
TTFB

Resource Hints: Preconnect & DNS-Prefetch

Establish early connections to critical third-party origins. Saves 100–300ms per origin by parallelising DNS, TCP, and TLS handshakes.

index.html
<head>
  <!-- Preconnect to critical origins(DNS + TCP + TLS) -->
  <link rel="preconnect" href="https:">//fonts.googleapis.com" />
  <link rel="preconnect" href="https:">//fonts.gstatic.com" crossorigin />
  <link rel="preconnect" href="https:">//cdn.yourapi.com" />

  <!-- DNS-prefetch as fallback for older browsers -->
  <link rel="dns-prefetch" href="https:">//analytics.example.com" />
  <link rel="dns-prefetch" href="https:">//maps.googleapis.com" />

  <!-- Prefetch next likely navigation -->
  <link rel="prefetch" href="/dashboard" />

  <!-- Prerender a high-probability next page(Chrome) -->
  <script type="speculationrules">
    {
      "prerender": [{
        "where": { "href_matches": "/pricing" },
        "eagerness": "moderate"
      }]
    }
  </script>
</head>
TTFB

Edge Caching & CDN Configuration

Serve content from edge nodes closest to users. Proper cache headers can reduce TTFB from 800ms to under 50ms for cached pages.

next.config.js
"color:#6a737d;font-style:italic">// next.config.js — Edge caching strategy
module.exports = {
  async headers() {
    return [
      {
        "color:#6a737d;font-style:italic">// Static assets — cache forever(they have hashed filenames)
        source: '/_next/static/(.*)',
        headers: [{
          key: 'Cache-Control',
          value: 'public, max-age=31536000, immutable',
        }],
      },
      {
        "color:#6a737d;font-style:italic">// Images — cache for 1 year
        source: '/images/(.*)',
        headers: [{
          key: 'Cache-Control',
          value: 'public, max-age=31536000, immutable',
        }],
      },
      {
        "color:#6a737d;font-style:italic">// HTML pages — serve stale while revalidating
        source: '/((?!api|_next).*)',
        headers: [{
          key: 'Cache-Control',
          value: 's-maxage=60, stale-while-revalidate=600',
        }],
      },
      {
        "color:#6a737d;font-style:italic">// API routes — short cache with revalidation
        source: '/api/(.*)',
        headers: [{
          key: 'Cache-Control',
          value: 'public, s-maxage=10, stale-while-revalidate=59',
        }],
      },
    ]
  },
}
TTFB

Streaming SSR with React 18

Stream HTML to the browser as it renders instead of waiting for the full page. Users see content faster and TTFB drops to near-zero for the first chunk.

page.tsx
"color:#6a737d;font-style:italic">// Next.js App Router — automatic streaming with Suspense
import { Suspense } from 'react'

"color:#6a737d;font-style:italic">// This component fetches data — server streams it when ready
async function ProductList() {
  const products = await fetch('https://api.store.com/products')
    .then(r => r.json())

  return (
    <div className="grid">
      {products.map(p => (
        <ProductCard key={p.id} product={p} />
      ))}
    </div>
  )
}

"color:#6a737d;font-style:italic">// Page component — shell is sent instantly
export default function StorePage() {
  return (
    <main>
      {"color:#6a737d;font-style:italic">/* This renders immediately in the first HTML chunk */}
      <h1>Our Products</h1>
      <p>Browse our latest collection</p>

      {"color:#6a737d;font-style:italic">/* This streams in when data is ready */}
      <Suspense fallback={<ProductGridSkeleton />}>
        <ProductList />
      </Suspense>
    </main>
  )
}

"color:#6a737d;font-style:italic">// The browser receives the <h1> and skeleton instantly,
"color:#6a737d;font-style:italic">// then the product list streams in once the API responds.
"color:#6a737d;font-style:italic">// TTFB for the first byte is effectively 0ms after edge cache.
Security

Content Security Policy Headers

CSP prevents XSS attacks and controls which resources browsers can load. A misconfigured third-party script cannot exfiltrate data with a strict CSP.

next.config.js
"color:#6a737d;font-style:italic">// next.config.js — Strict Content Security Policy
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'nonce-RANDOM_NONCE' https:">//cdnjs.cloudflare.com",
              "style-src 'self' 'unsafe-inline' https:">//fonts.googleapis.com",
              "font-src 'self' https:">//fonts.gstatic.com",
              "img-src 'self' data: https:",
              "connect-src 'self' https:">//api.yourservice.com",
              "frame-ancestors 'none'",
              "base-uri 'self'",
              "form-action 'self'",
            ].join('; '),
          },
          { key: 'X-Frame-Options', value: 'DENY' },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
          { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
        ],
      },
    ]
  },
}
Security

Subresource Integrity (SRI) for Third-Party Scripts

SRI ensures third-party scripts haven't been tampered with. If a CDN is compromised, the browser rejects the script. Prevents supply-chain attacks.

index.html
<!-- Subresource Integrity — browser verifies hash before executing -->
<script
  src="https:">//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"
  integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ=="
  crossorigin="anonymous"
  referrerpolicy="no-referrer"
  defer
></script>

<!-- Generate SRI hash for any asset: -->
<!-- $ openssl dgst -sha512 -binary lodash.min.js | openssl base64 -A -->

<!-- Or use the online generator: https:"color:#6a737d;font-style:italic">//www.srihash.org -->

<!-- In Next.js — add to next.config.js -->
"color:#6a737d;font-style:italic">// next.config.js
module.exports = {
  "color:#6a737d;font-style:italic">// Automatically adds SRI to _next/static scripts
  generateBuildId: async () => 'my-build-id',
  experimental: {
    sri: {
      algorithm: 'sha512',
    },
  },
}