Skip to content

Last updated: First published:

Loading and Swapping

Astro’s support for view transitions defines two functions that are called during view transition processing and can be overridden by the user. The first is the loader() function, which is executed during the preparation phase, and the second is the swap() function, which is executed during the swap DOM phase.

The loader() function provides the new content for the next page and swap() function implements the swapping of the DOM as part of the soft loading.

Both functions can be extended or replaced with custom code, see below.

Some functionalities like the triggering of events and starting of animations is not covered by loader() and swap() and can not be exchanged by redefining them. Other steps like updating history and scroll position can also not be replaced but their effect can be overridden, for example in a listener for astro:after-swap.

Semantics

Together, the loader() and the swap() function provide the main functionality of Astro’s view transition processing. The view transition API provides an update callback to switch the current browser document to its new state. While this callback is executing, rendering is stopped and the browser’s view is frozen. Therefore it is important that updating the DOM happens really fast. But how can you load the next page over the network and be fast anyhow?

This is the main reason for the separation into a preparation phase with the loader() function and a swap phase with the swap() function. The loader() function does the heavy lifting. All the time consuming actions like fetching and parsing the target document will happen inside the loader() function. During the preparation phase, the user can still fully interact with the current page. Once the preparation is done, the swap() function can directly access the future DOM in memory and quickly swap in the future DOM for the current DOM.

Loader

The loader() function runs between the astro:before-preparation and astro:after-preparation events. Its purpose is to load or otherwise construct the target DOM of the navigation. It stores the result in the newDocument property of the preparation event. During the subsequent swap phase, this value will also be available in the newDocument property of the swap event.

The loader() function is a asynchronous function and it can call and await other asynchronous functions. Astro will wait for settlement of the promise returned by loader() before it fires the astro:after-preparation event.

Swap

The swap() function updates the current DOM at window.document to reflect the DOM that is stored in the newDocument property of the swap event. This is the document that the loader() created during the preparation phase.

The swap() function should only update the DOM. It is a synchronous function that can not wait for asynchronous task to complete. All prerequisites that need to be fulfilled for swap() to do its job should be moved out of the swap phase into the preparation phase and into the loader() function.

Extending Defaults

Often you might want to add some extra code at the beginning or at the end of the loader() or swap() function. This can be done using the patterns shown in the next two section. They look rather similar. They main difference between them is that loader() is a asynchronous function and swap() is not.

Extending loader()

The loader() function can be overwritten in the astro:before-preparation event.

This is the pattern for the extension of the loader() function:

AugmentedLoader.astro
1
<script>
5 collapsed lines
2
import {
3
isTransitionBeforePreparationEvent,
4
TRANSITION_BEFORE_PREPARATION,
5
} from "astro:transitions/client";
6
7
/*
8
* Add your code before and/or after the original loader()
9
*/
10
document.addEventListener(TRANSITION_BEFORE_PREPARATION, augmentLoader);
11
function augmentLoader(event: Event) {
12
if (isTransitionBeforePreparationEvent(event)) {
13
const originalLoader = event.loader;
14
event.loader = async () => {
15
// do stuff before the original loader
16
await originalLoader(); // call original loader
17
// do stuff after the original loader
18
};
19
}
20
}
21
</script>

How can you be sure that what you named originalLoader is indeed is the original loader? You can’t. it might as well be a new function some other event listener assigned to the loader property before. Several listener might cooperate and build a Chain of Responsibility by wrapping the loader in several layers like an onion.

Extending swap()

And this is the chaining pattern for extending the swap() function.

AugmentedSwap.astro
1
<script>
5 collapsed lines
2
import {
3
isTransitionBeforeSwapEvent,
4
TRANSITION_BEFORE_SWAP,
5
} from "astro:transitions/client";
6
7
/*
8
* Add your code before and/or after the original swap()
9
*/
10
document.addEventListener(TRANSITION_BEFORE_SWAP, augmentSwap);
11
function augmentSwap(event: Event) {
12
if (isTransitionBeforeSwapEvent(event)) {
13
const originalSwap = event.swap;
14
event.swap = () => {
15
// do stuff before the original swap
16
originalSwap(); // call original swap
17
// do stuff after the original swap
18
};
19
}
20
}
21
</script>

The Inner Workings

There are also use cases where you want to replace the default behavior of the loader() or swap() functions. In this case, you may want to know what these functions do in detail so that you can provide useful replacements.

Loader

The loader() function can be overwritten in the astro:before-preparation event. Its purpose is use the events data, especially event.to, to define event.newDocument, the future DOM that should be later swapped in.

These are implementation details, not part of the API and might change without prior notice. Here is a sketch of what the default implementation does:

  1. Call fetch() to load the new document from the event.to URL. This also includes the correct handling of form data as POST requests and updating event.to if the fetch was redirected to another URL.

  2. Make sure that the media type of the response indicates a valid form of HTML document.

  3. Parse the response into event.newDocument.

  4. Imitate browsers default behavior and remove <no-script> elements from the parsed document.

  5. If the target page does not support the view transition router, fall back to a full page load, unless we are posting form data.

  6. Preload all style links.

  7. In DEV mode, simulate the initialization of client:only components and add their dynamically generated styles to event.newDocument.

Swap

The swap() function can be overridden in the astro:before-swap event. Its purpose is to quickly update the current DOM in window.document so that it resembles the document stored in event.newDocument.

These are implementation details, not part of the API and might change without prior notice. Here is a sketch of what the default implementation does:

  1. Determine which of the scripts in event.newDocument should not be run between astro:after-swap and astro:page-load. These are the script that were already part of the previous page and were not marked with data-astro-rerun. Mark those scripts as already executed in event.newDocument.
  2. Swap the attributes of the <html> element. Typically the current attributes are just replaced with the ones from the new DOM.
  3. Swap the children of the <head> element. Instead of removing all existing elements and then add the new ones, Astro keeps some elements untouched. These are a) style sheet links with same href that are present in both DOMs and b) elements with same data-astro-transition-persist name in both DOMs.
  4. Save the current input focus and text selection
  5. Replace window.document.body with the event.newDocument.body
  6. Replace elements in the now current body with the corresponding elements from the old body if they are marked with the same data-astro-transition-persist name in both DOMs. Elements with the same data-astro-transition-persist name can have different element types, attributes and children.
  7. Restore the previous input focus and text selection.

For attributes of the <html> element and child elements of the <head> element, the order might change during swap. The <html> and <head> elements themselves as well as window.document are never removed or added.

Event listeners on window and window.document are not affected by the swap. References to elements of the old DOM that didn’t make it to the current DOM are invalid after swap. Event listeners for elements that are no longer part of the current DOM after the swap are never called and newly added elements have no listeners unless they have been explicitly set, e.g. by using the astro:after-swap or astro:page-load handlers.