---
area: "Learn"
parent: "History chapter"
url: "https://unpoly.com/learn/restoring-history"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [History chapter](https://unpoly.com/learn/history.md)</nav>

# Restoring history

When the user presses the browser's Back or Forward button, Unpoly restores the earlier page in place: it fetches the restored URL, renders it into the `<body>` and brings back the scroll positions and focus the user left behind. You can restore a different fragment, or handle restoration with your own code.

<nav aria-label="Contents">

#### Contents

- [What happens when the user goes back](#default-behavior)
- [Restoring a different fragment](#restore-targets)
- [Customizing the restoration](#custom-behavior)
- [Which entries Unpoly restores](#handled-entries)
- [Linking to the previous page](#back-link)

</nav>

## What happens when the user goes back {#default-behavior}

The browser changes the address bar on its own. Unpoly then restores the content for the restored URL:

1. The restored URL is requested with `GET`. A [cached](https://unpoly.com/learn/caching.md) response is used when available.
2. Any open [overlays](https://unpoly.com/learn/history-in-overlays.md#restoration) are closed. The restored page is rendered in the root layer.
3. The response's `<body>` replaces the current `<body>`.
4. Scroll positions are restored to where they were when the user left the page.
5. Focus and text selection are restored to the element the user was in.
6. If the cached content has [expired](https://unpoly.com/learn/caching.md#revalidation), it is revalidated with the server.

Scroll positions and focus are remembered for each layer until the next full page load. If no positions are known, for example after a full page reload, Unpoly scrolls and focuses as it does when [following a link](https://unpoly.com/learn/scrolling.md#auto).

Requests that are still loading for the restored fragment are aborted, so a late response cannot overwrite the restored page. See [Aborting requests](https://unpoly.com/learn/aborting-requests.md).

Unpoly renders whatever the server responds, even an error page. The browser has already changed the URL, so the page must follow. When the server cannot be reached, the address bar shows the restored URL while the page keeps its content. You can handle this case like any other [connection loss](https://unpoly.com/learn/network-issues.md).

When only the `#hash` changed between two history entries, nothing is rendered. Unpoly scrolls to the element matching the hash, honoring any [obstructing layout elements](https://unpoly.com/learn/scroll-tuning.md#fixed-layout-elements-obstructing-the-viewport).

## Restoring a different fragment {#restore-targets}

By default Unpoly replaces the entire `<body>`, in case the earlier page used a different layout. If your layout never changes, you can restore a smaller fragment and keep elements like a navigation bar or a sidebar alive. Configure [`up.history.config.restoreTargets`](https://unpoly.com/api/up.history.config.md#config.restoreTargets) with a list of selectors:

```js
up.history.config.restoreTargets = ['.content', 'body']
```

Unpoly updates the first selector that matches both the current page and the server response. Here `.content` is restored when both documents have one, and the `<body>` otherwise.

## Customizing the restoration {#custom-behavior}

Before Unpoly renders the restored content, it emits an [`up:location:restore`](https://unpoly.com/api/up:location:restore.md) event. Listeners can inspect and change the [render options](https://unpoly.com/api/up.render.md#parameters) in `event.renderOptions`:

```js
up.on('up:location:restore', function(event) {
  // Update a different fragment when restoring /special-path
  if (event.location === '/special-path') {
    event.renderOptions.target = '#other'
  }
})
```

You can also replace Unpoly's render pass with your own restoration code by preventing the event. Unpoly then changes no element on the page:

```js
up.on('up:location:restore', function(event) {
  // Stop Unpoly from rendering anything
  event.preventDefault()

  // We will render ourselves
  document.body.innerText = `Restored content for ${event.location}!`
})
```

When [`up:location:restore`](https://unpoly.com/api/up:location:restore.md) is emitted, the [browser location](https://unpoly.com/api/up.history.location.md) has already been updated. Neither preventing the event nor changing render options can undo that.

> #### Important
>
> Custom restoration code should not push new history entries.

## Which entries Unpoly restores {#handled-entries}

Unpoly restores history entries that it _owns_:

- The entry of the initial page load.
- Entries placed by Unpoly while [updating history](https://unpoly.com/learn/updating-history.md), or through [`up.history.push()`](https://unpoly.com/api/up.history.push.md) and [`up.history.replace()`](https://unpoly.com/api/up.history.replace.md).
- Entries for a changed `#hash` on a location that Unpoly owns, whether from a link or typed into the address bar.

An entry that another script pushed with `history.pushState()` is left alone. Unpoly assumes that script will restore its own state.

To take over or hand off an entry, listen to [`up:location:changed`](https://unpoly.com/api/up:location:changed.md) and set `event.willHandle`:

```js
up.on('up:location:changed', function(event) {
  // Let Unpoly restore entries pushed by a third-party script
  if (event.location.startsWith('/gallery/')) {
    event.willHandle = true
  }
})
```

Setting `event.willHandle = false` stops Unpoly from restoring an entry it owns. Your listener can then restore the page with its own code.

> #### Note
>
> If the initial page was loaded with a `POST` request, reloading its URL with `GET` would render something else. Your server can tell Unpoly with an [`_up_method`](https://unpoly.com/api/_up_method.md) cookie, so Unpoly does not claim that entry.

## Linking to the previous page {#back-link}

To offer a _Back_ link inside the page, set an [`[up-back]`](https://unpoly.com/api/up-back.md) attribute:

```html
<a href="/posts" up-back>Back</a>
```

When Unpoly knows the previous URL, clicking the link [navigates](https://unpoly.com/learn/navigation-defaults.md) there and restores the earlier scroll position. When no previous URL is known, for example right after the initial page load, the link follows its `[href]` as usual.

Unlike the browser's Back button, an [`[up-back]`](https://unpoly.com/api/up-back.md) link does not call `history.back()`. It follows the previous URL like any other link, moving forward in history.

The previous URL is also available as [`up.history.previousLocation`](https://unpoly.com/api/up.history.previousLocation.md).

<nav aria-label="Reading order">

- [Previous: Updating history](https://unpoly.com/learn/updating-history.md)
- [Next: Tracking page views](https://unpoly.com/learn/tracking-page-views.md)

</nav>

