HTTP

Status Code

103 Early Hints

The server sends preliminary response headers to help the client start preloading resources. Learn how 103 Early Hints improves page load performance.

6 min read advanced Try in Playground

What is 103 Early Hints?

TL;DR: Server sends resource hints before final response to speed up page loading. Browser can start downloading CSS/JS while waiting.

A 103 Early Hints status code is an informational response that lets the server send preliminary HTTP headers while it’s still preparing the full response. Think of it like a restaurant server bringing you bread and water while the kitchen prepares your main course—it keeps you occupied and improves the overall experience.

This status code is primarily used to hint at resources the client should start preloading (like CSS, JavaScript, fonts) before the final HTML response is ready, significantly improving page load performance.

When Does This Happen?

You’ll see a 103 Early Hints response in these common situations:

1. Server-Side Rendering (SSR)

Server generating HTML (takes 200ms)
→ Sends 103 with CSS/JS hints immediately
→ Browser starts downloading while waiting
→ Final HTML arrives with resources already loading

2. Database-Heavy Pages

Querying multiple databases for content
→ Send 103 with known static assets
→ Database queries complete
→ Return final response

3. Authenticated Pages

Validating user session (slow)
→ Send 103 with common resources
→ Complete auth check
→ Return personalized content

4. API-Dependent Pages

Waiting for third-party API responses
→ Send 103 with static assets
→ APIs respond
→ Render final page

5. Edge Computing

CDN edge processing request
→ Sends 103 with resource hints
→ Origin server generates response
→ Returns final content

Example Responses

Basic Early Hints:

HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="/style.css">
  <script src="/script.js"></script>
</head>
<body>...</body>
</html>
```text

**Multiple Resources:**

```http
HTTP/1.1 103 Early Hints
Link: </critical.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
Link: </font.woff2>; rel=preload; as=font; crossorigin
Link: </hero.jpg>; rel=preload; as=image
Link: https://cdn.example.com; rel=preconnect

HTTP/1.1 200 OK
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="/critical.css">
  <link rel="preload" href="/font.woff2" as="font" crossorigin>
</head>
<body>
  <img src="/hero.jpg" alt="Hero">
  <script src="/app.js"></script>
</body>
</html>

With CSP Headers:

HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </main.js>; rel=preload; as=script
Content-Security-Policy: default-src 'self'

HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: default-src 'self'
Content-Length: 5678

<!DOCTYPE html>
<html>...</html>
```text

## Real-World Example

Imagine you're serving a Next.js application with server-side rendering:

**Client Request:**

```http
GET /dashboard HTTP/1.1
Host: app.example.com
User-Agent: Mozilla/5.0...
Cookie: session=abc123

Early Hints Response (sent immediately):

HTTP/1.1 103 Early Hints
Link: </_next/static/css/main.css>; rel=preload; as=style
Link: </_next/static/js/main.js>; rel=preload; as=script
Link: </_next/static/js/webpack.js>; rel=preload; as=script
Link: </fonts/inter.woff2>; rel=preload; as=font; crossorigin
Link: https://cdn.example.com; rel=preconnect
Link: https://api.example.com; rel=preconnect

```text

**Browser starts preloading resources...**

**Final Response (200ms later):**

```http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: private, no-cache
Content-Length: 45678

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="/_next/static/css/main.css">
  <link rel="preconnect" href="https://cdn.example.com">
  <link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin>
</head>
<body>
  <div id="__next">
    <div class="dashboard">
      <!-- Rendered dashboard content -->
    </div>
  </div>
  <script src="/_next/static/js/webpack.js"></script>
  <script src="/_next/static/js/main.js"></script>
</body>
</html>

103 vs Other Informational Codes

CodeMeaningPurposeBrowser Action
103Early HintsPerformance optimizationStart preloading resources
100ContinueUpload optimizationSend request body
101Switching ProtocolsProtocol upgradeSwitch to new protocol
102ProcessingLong operation statusWait for completion

Important Characteristics

Timing Matters:

Request received → Immediately send 103 → Process request → Send final response
                   (0-10ms)                 (50-500ms)

Link Header Format:

Link: <URL>; rel=preload; as=TYPE; [additional-params]

Examples:
Link: </style.css>; rel=preload; as=style
Link: </font.woff2>; rel=preload; as=font; crossorigin
Link: https://cdn.com; rel=preconnect
```text

**Multiple 103 Responses:**

- Servers can send multiple 103 responses
- Each can contain different hints
- Useful when hints are discovered progressively

**Compatibility:**

```http
Supported: Chrome 103+, Edge 103+, Firefox (partial)
Unsupported: Safari (as of early 2026)
Fallback: Browsers ignore 103, wait for final response
```text

## Common Mistakes

**❌ Hinting resources not in final response**

```http
103 Early Hints:
Link: </unused.css>; rel=preload; as=style  ← Never used

200 OK:
<html>
  <!-- unused.css is never referenced -->
</html>

❌ Sending 103 too late

// Bad: After processing is done
await processRequest() // 500ms
res.writeEarlyHints() // Too late!
res.send(html)
```text

**❌ Overloading with hints**

```http
HTTP/1.1 103 Early Hints
Link: </1.css>; rel=preload; as=style
Link: </2.css>; rel=preload; as=style
Link: </3.css>; rel=preload; as=style
... 50 more resources ...  ← Too many!

✅ Correct usage

// Good: Send immediately, only critical resources
app.get('/page', async (req, res) => {
  // Send early hints ASAP
  res.writeEarlyHints({
    link: ['</critical.css>; rel=preload; as=style', '</app.js>; rel=preload; as=script']
  })

  // Then process request
  const data = await fetchData()
  res.send(renderHTML(data))
})
```text

## Best Practices

**Hint Only Critical Resources:**

```http
HTTP/1.1 103 Early Hints
Link: </critical.css>; rel=preload; as=style     ✓ Above-fold CSS
Link: </hero.jpg>; rel=preload; as=image         ✓ LCP image
Link: </analytics.js>; rel=preload; as=script    ✗ Non-critical

Send Early Hints ASAP:

// Express.js example
app.get('/dashboard', async (req, res) => {
  // Send hints before ANY processing
  res.writeEarlyHints({
    link: [
      '</static/main.css>; rel=preload; as=style',
      '</static/app.js>; rel=preload; as=script',
      'https://fonts.googleapis.com; rel=preconnect'
    ]
  })

  // Now do expensive operations
  const userData = await db.query('SELECT * FROM users...')
  const analytics = await fetchAnalytics()

  res.send(renderPage(userData, analytics))
})
```javascript

**Consistent Hints Across Requests:**

```javascript
// Cache hints for similar pages
const dashboardHints = [
  '</dashboard.css>; rel=preload; as=style',
  '</dashboard.js>; rel=preload; as=script'
]

app.get('/dashboard/*', (req, res) => {
  res.writeEarlyHints({ link: dashboardHints })
  // ... rest of handler
})

Monitor Performance Impact:

// Track Early Hints effectiveness
const start = Date.now()
res.writeEarlyHints({ link: hints })

res.on('finish', () => {
  const duration = Date.now() - start
  metrics.record('early_hints.duration', duration)
})
```javascript

## Implementation Examples

**Node.js (Native HTTP):**

```javascript
const http = require('http')

http
  .createServer((req, res) => {
    // Send 103 Early Hints
    res.writeEarlyHints({
      link: ['</style.css>; rel=preload; as=style', '</script.js>; rel=preload; as=script']
    })

    // Simulate processing time
    setTimeout(() => {
      res.writeHead(200, { 'Content-Type': 'text/html' })
      res.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <link rel="stylesheet" href="/style.css">
        </head>
        <body>
          <h1>Hello World</h1>
          <script src="/script.js"></script>
        </body>
      </html>
    `)
    }, 100)
  })
  .listen(3000)

Express.js:

const express = require('express')
const app = express()

app.get('/', async (req, res) => {
  // Send early hints immediately
  if (res.writeEarlyHints) {
    res.writeEarlyHints({
      link: [
        '</public/main.css>; rel=preload; as=style',
        '</public/app.js>; rel=preload; as=script',
        'https://fonts.gstatic.com; rel=preconnect; crossorigin'
      ]
    })
  }

  // Expensive operation
  const data = await fetchDataFromDB()

  res.send(renderHTML(data))
})
```javascript

**Cloudflare Worker:**

```javascript
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  // Send Early Hints
  const earlyHintsResponse = new Response(null, {
    status: 103,
    headers: {
      Link: ['</style.css>; rel=preload; as=style', '</app.js>; rel=preload; as=script'].join(', ')
    }
  })

  // Note: Cloudflare automatically handles 103
  // This is for illustration

  // Fetch from origin
  const response = await fetch(request)
  return response
}

Nginx Configuration:

# Nginx doesn't natively support 103, but can be added via modules
# or handled by upstream application server

location / {
    # Application sends 103
    proxy_pass http://backend;
    proxy_http_version 1.1;
}
```text

## Performance Impact

**Before Early Hints:**

```text
Request → Wait 200ms → Receive HTML → Parse → Discover resources → Download

                                                            Start at ~200ms
```text

**With Early Hints:**

```text
Request103 (5ms) → Start downloads → Wait 195ms → Receive HTML
          ↑                                           ↑
     Discover resources                      Resources already loading

Typical Improvements:

  • First Contentful Paint (FCP): 10-20% faster
  • Largest Contentful Paint (LCP): 15-30% faster
  • Time to Interactive (TTI): 10-20% faster

Try It Yourself

Visit our request builder and see 103 Early Hints in action:

  1. Set method to GET
  2. Set path to /early-hints-demo
  3. Click Send request
  4. Watch for 103 response with Link headers
  5. Observe resource preloading before final response

Frequently Asked Questions

What does 103 Early Hints mean?

A 103 Early Hints response lets the server send Link headers before the final response, allowing browsers to preload critical resources like CSS and fonts while waiting for the main response.

How does 103 Early Hints improve performance?

It reduces page load time by letting browsers start fetching critical resources before the server finishes generating the HTML. This parallelizes resource loading with server processing.

Which browsers support 103 Early Hints?

Chrome 103+, Edge 103+, and Firefox 102+ support Early Hints. Safari support is limited. Always test browser compatibility for your target audience.

How do I implement 103 Early Hints?

Configure your server or CDN to send 103 responses with Link headers for critical resources. Many CDNs like Cloudflare support automatic Early Hints generation.

Keep Learning