---
area: "Learn"
parent: "Backend integration chapter"
url: "https://unpoly.com/learn/optimizing-responses"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Backend integration chapter](https://unpoly.com/learn/backend-integration.md)</nav>

# Optimizing responses

Your server can shorten or customize its responses by reading the request headers that Unpoly sends, like the targeted selector or the mode of the updating layer. This is optional: Unpoly only takes the targeted fragment from any response and discards the rest, except [hungry elements](https://unpoly.com/learn/hungry-elements.md) and the `<head>`, so a full HTML page is always a valid answer.

<nav aria-label="Contents">

#### Contents

- [Rendering only the targeted fragment](#target)
- [Rendering different content for overlays](#mode)
- [Detecting a full page load](#version)
- [Rendering content that depends on layer context](#context)
- [Caching optimized responses](#vary)

</nav>

## Rendering only the targeted fragment {#target}

Unpoly sends the [target selector](https://unpoly.com/learn/targeting-fragments.md) in an [`X-Up-Target`](https://unpoly.com/api/X-Up-Target.md) request header. When a link targets a `.menu` element, the server sees a request like this:

```http
GET /sitemap HTTP/1.1
X-Up-Target: .menu
```

The server may answer with a full page and let Unpoly pick out the `.menu` element. Or it may skip the rest of the page and render only what was asked for. A response that was tailored to the target must list the header in a [`Vary`](https://unpoly.com/api/Vary.md) header, so the [cache](#vary) does not reuse it for other targets:

```http
HTTP/1.1 200 OK
Vary: X-Up-Target

<div class="menu">...</div>
```

A typical use is to skip expensive parts of the layout, like a sidebar or a navigation bar, when they are not part of the target.

[`X-Up-Target`](https://unpoly.com/api/X-Up-Target.md) does not list [hungry elements](https://unpoly.com/learn/hungry-elements.md) like [flashes](https://unpoly.com/learn/flashes.md). They are updated whenever a response happens to contain them.

A request may target [multiple fragments](https://unpoly.com/learn/targeting-fragments.md#multiple). The header then lists all selectors, separated by commas, and the response should contain an element for each:

```http
X-Up-Target: .menu, .breadcrumbs
```

A request made without a target, like a plain [`up.request('/sitemap')`](https://unpoly.com/api/up.request.md) call, has no [`X-Up-Target`](https://unpoly.com/api/X-Up-Target.md) header. Treat a missing header as a request for the full page.

### Setting the title without a `<head>` {#title}

A shortened response usually has no `<head>` and therefore no `<title>`. To still update the document title, send it as an [`X-Up-Title`](https://unpoly.com/api/X-Up-Title.md) response header, encoded as a [relaxed JSON](https://unpoly.com/api/relaxed-json.md) string:

```http
HTTP/1.1 200 OK
Vary: X-Up-Target
X-Up-Title: "Sitemap"

<div class="menu">...</div>
```

A response without a `<head>` has two more limits: Unpoly keeps the current [meta tags](https://unpoly.com/learn/updating-history.md#meta-tags), and it cannot [detect a new deployment](https://unpoly.com/learn/handling-asset-changes.md).

### Error responses target a different fragment {#fail-target}

When the server responds with an error code, Unpoly renders a [different target](https://unpoly.com/learn/error-responses.md#fail-options). Form submissions use this to update the `<form>` with validation errors instead of the success target. The fail target is sent as a separate [`X-Up-Fail-Target`](https://unpoly.com/api/X-Up-Fail-Target.md) header:

```http
POST /users HTTP/1.1
X-Up-Target: .content
X-Up-Fail-Target: #signup-form
```

A server that optimizes its responses should render for [`X-Up-Target`](https://unpoly.com/api/X-Up-Target.md) on success, and for [`X-Up-Fail-Target`](https://unpoly.com/api/X-Up-Fail-Target.md) when it responds with an error status like `422`.

## Rendering different content for overlays {#mode}

Unpoly sends the [mode](https://unpoly.com/api/up.layer.mode.md) of the updating layer in an [`X-Up-Mode`](https://unpoly.com/api/X-Up-Mode.md) request header. A link that opens a drawer overlay produces a request like this:

```http
GET /users/5 HTTP/1.1
X-Up-Mode: drawer
X-Up-Target: main
```

The server may render a leaner version for overlays, e.g. without the site navigation. As before, the [`Vary`](https://unpoly.com/api/Vary.md) header tells Unpoly that the response depends on the mode:

```http
HTTP/1.1 200 OK
Vary: X-Up-Mode

<main>...</main>
```

For the root layer the header is `X-Up-Mode: root`. The mode of the layer that renders an error response is sent as [`X-Up-Fail-Mode`](https://unpoly.com/api/X-Up-Fail-Mode.md).

## Detecting a full page load {#version}

Every request made through Unpoly carries an [`X-Up-Version`](https://unpoly.com/api/X-Up-Version.md) header with the frontend's version:

```http
X-Up-Version: 3.15.0
```

A request without this header is a full page load, where the browser renders the entire page. Server-side libraries use this to answer questions about the request safely. For example, [`unpoly-rails`](https://github.com/unpoly/unpoly-rails) answers `up.target?('.sidebar')` with `true` for every request that isn't from Unpoly, so a template never omits content from a full page load.

To omit content from a response, check [`X-Up-Target`](#target) instead. It tells you exactly which fragments Unpoly needs.

## Rendering content that depends on layer context {#context}

A layer can carry a [context](https://unpoly.com/learn/layer-context.md) object, e.g. to reuse a screen in an overlay with a different heading. Unpoly sends it as JSON in an [`X-Up-Context`](https://unpoly.com/api/X-Up-Context.md) request header:

```http
X-Up-Context: { "title": "Choose a company contact" }
X-Up-Target: main
```

A response that depends on the context lists it in its [`Vary`](https://unpoly.com/api/Vary.md) header. See [Layer context](https://unpoly.com/learn/layer-context.md) for the full exchange.

## Caching optimized responses {#vary}

Unpoly [caches](https://unpoly.com/learn/caching.md) responses and reuses them for later requests to the same URL. A response that was tailored to a request header must not be reused for a request with a different header value: a response that only contains `.menu` cannot serve a request for `.sidebar`.

Request headers that influenced a response should therefore be listed in a [`Vary`](https://unpoly.com/api/Vary.md) response header. This tells Unpoly to partition its cache for that URL, so that each header value gets a separate cache entry:

```http
HTTP/1.1 200 OK
Vary: X-Up-Target, X-Up-Mode

<div class="menu">...</div>
```

After seeing `Vary: X-Up-Target`, a cached response for `.menu` is no longer a cache hit for a request targeting a different selector.

[`Vary`](https://unpoly.com/api/Vary.md) is a standard HTTP header, so it also keeps the browser's HTTP cache and shared caches like CDNs from serving a shortened response to a full page load. Check that your CDN honors [`Vary`](https://unpoly.com/api/Vary.md) for custom headers, or don't let it cache these responses.

You can set the [`Vary`](https://unpoly.com/api/Vary.md) header from your own server code. Some [protocol implementations](https://unpoly.com/learn/protocol-implementations.md), like `unpoly-rails`, set it for you once you read a header.

### How cache entries are matched {#cache-matching}

By default cached responses will match all requests to the same URL.

When a response has a [`Vary`](https://unpoly.com/api/Vary.md) header, matching requests must additionally have the same values for all listed headers:

|  | 🠦 `X-Up-Target: .foo` 🠤 `Vary: X-Up-Target` |
| --- | --- |
| 🠦 `X-Up-Target: .foo` | ✔️ cache hit |
| 🠦 `X-Up-Target: .bar` | ❌ cache miss |
| 🠦 _No `X-Up-Target`_ | ❌ cache miss |

When a response has _no_ [`Vary`](https://unpoly.com/api/Vary.md) header, that response is a cache hit for _all_ requests to the URL, regardless of target:

|  | 🠦 `X-Up-Target: .foo` 🠤 _No `Vary`_ |
| --- | --- |
| 🠦 `X-Up-Target: .foo` | ✔️ cache hit |
| 🠦 `X-Up-Target: .bar` | ✔️ cache hit |
| 🠦 _No `X-Up-Target`_ | ✔️ cache hit |

Requests can [target multiple fragments](https://unpoly.com/learn/targeting-fragments.md#multiple) by separating selectors with a comma. If the server replies with `Vary: X-Up-Target`, that response is a cache hit for each individual selector:

|  | 🠦 `X-Up-Target: .foo, .bar` 🠤 `Vary: X-Up-Target` |
| --- | --- |
| 🠦 `X-Up-Target: .foo` | ✔️ cache hit |
| 🠦 `X-Up-Target: .bar` | ✔️ cache hit |
| 🠦 `X-Up-Target: .foo, .bar` | ✔️ cache hit |
| 🠦 `X-Up-Target: .bar, .foo` | ✔️ cache hit |
| 🠦 `X-Up-Target: .baz` | ❌ cache miss |
| 🠦 `X-Up-Target: .foo, .baz` | ❌ cache miss |
| 🠦 _No `X-Up-Target`_ | ❌ cache miss |

<nav aria-label="Reading order">

- [Previous: Backend integration](https://unpoly.com/learn/backend-integration.md)
- [Next: Conditional requests](https://unpoly.com/learn/conditional-requests.md)

</nav>

