Prefetch и предзагрузка ресурсов

Prefetch в контексте Stimulus — это управляемая предзагрузка данных, шаблонов и вспомогательных ресурсов на стороне клиента до момента фактического взаимодействия пользователя. Основная идея — сократить задержки интерфейса за счёт выполнения потенциально дорогих операций заранее, но без нарушения принципов прогрессивного улучшения и без избыточной нагрузки на сеть.

Stimulus не предоставляет отдельного встроенного API под названием prefetch, однако архитектура фреймворка идеально подходит для реализации предзагрузки через контроллеры, lifecycle-хуки и интеграцию с браузерными механизмами (link rel="prefetch", fetch, IntersectionObserver).


Встраивание prefetch в жизненный цикл контроллера

Stimulus-контроллер проходит несколько фаз жизни, каждая из которых может быть использована для предзагрузки ресурсов:

  • initialize() — инициализация, логика без доступа к DOM
  • connect() — контроллер подключён к DOM
  • disconnect() — удаление из DOM

Для prefetch чаще всего используется connect(), так как в этот момент доступна информация о состоянии страницы и атрибутах элемента.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.prefetchData()
  }

  async prefetchData() {
    await fetch("/api/products")
  }
}

Такой подход гарантирует, что данные начнут загружаться сразу после появления релевантного DOM-узла, но до пользовательского действия.


Условная предзагрузка на основе данных

Prefetch не должен быть безусловным. Стимулюс позволяет гибко управлять логикой через values и data-атрибуты.

<div
  data-controller="products"
  data-products-prefetch-value="true">
</div>
export default class extends Controller {
  static values = { prefetch: Boolean }

  connect() {
    if (this.prefetchValue) {
      this.loadProducts()
    }
  }
}

Такой механизм делает prefetch декларативным и легко управляемым на уровне шаблонов.


Предзагрузка по намерению пользователя

Одной из наиболее эффективных стратегий является prefetch по косвенному намерению: наведение курсора, фокус, начало скролла.

Stimulus отлично подходит для такой модели, поскольку события объявляются декларативно.

<a
  href="/profile"
  data-controller="prefetch"
  data-action="mouseenter->prefetch#load">
  Профиль
</a>
export default class extends Controller {
  load() {
    fetch(this.element.href, { credentials: "same-origin" })
  }
}

Ресурс будет загружен в момент, когда вероятность перехода высока, но до фактического клика.


Prefetch и IntersectionObserver

Для тяжёлых ресурсов (модальные окна, формы, дополнительные секции) эффективна предзагрузка при приближении элемента к области видимости.

export default class extends Controller {
  connect() {
    this.observer = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        this.prefetch()
        this.observer.disconnect()
      }
    })

    this.observer.observe(this.element)
  }

  prefetch() {
    fetch("/modal/content")
  }
}

Stimulus здесь выступает в роли координатора, а браузер — в роли оптимизатора.


В некоторых случаях предпочтительнее задействовать нативный prefetch браузера. Stimulus может динамически добавлять соответствующие теги.

export default class extends Controller {
  connect() {
    const link = document.createElement("link")
    link.rel = "prefetch"
    link.href = "/assets/chart.js"
    document.head.appendChild(link)
  }
}

Преимущество — браузер сам определяет приоритет загрузки и не блокирует основной поток.


Кэширование и повторное использование данных

Prefetch без кэширования быстро теряет смысл. Часто используется простой in-memory кэш внутри контроллера или общего модуля.

let cache = null

export default class extends Controller {
  async prefetch() {
    if (!cache) {
      cache = await fetch("/api/stats").then(r => r.json())
    }
  }
}

Stimulus не навязывает архитектуру хранения данных, что позволяет комбинировать его с любыми стратегиями кэширования.


Изоляция prefetch-логики

Хорошей практикой является вынос предзагрузки в отдельные контроллеры, не смешивая её с UI-логикой.

<div data-controller="prefetch" data-prefetch-url-value="/settings"></div>
export default class extends Controller {
  static values = { url: String }

  connect() {
    fetch(this.urlValue)
  }
}

Такой контроллер можно переиспользовать на разных страницах без дублирования кода.


Управление приоритетами и отмена запросов

Для сложных интерфейсов важно уметь отменять prefetch при изменении контекста. Используется AbortController.

export default class extends Controller {
  connect() {
    this.abortController = new AbortController()
    fetch("/api/preview", { signal: this.abortController.signal })
  }

  disconnect() {
    this.abortController.abort()
  }
}

Это предотвращает утечки ресурсов при динамическом обновлении DOM.


Prefetch в связке с Turbo

В экосистеме Hotwire Stimulus часто используется совместно с Turbo. Prefetch может подготавливать данные или состояния перед Turbo-навигацией, не вмешиваясь в сам механизм переходов.

Типичный сценарий — предзагрузка JSON, который затем используется для мгновенного рендера после turbo:load.


Ограничения и балансировка нагрузки

Prefetch не должен:

  • загружать критические ресурсы конкурируя с основным рендером
  • выполняться на медленных соединениях без проверки navigator.connection
  • инициировать запросы без вероятности использования

Stimulus позволяет встроить эти проверки локально и декларативно, не усложняя глобальную архитектуру.


Архитектурное значение prefetch в Stimulus

Prefetch в Stimulus — это не отдельная технология, а паттерн. Он реализуется через небольшие, изолированные контроллеры, использующие нативные возможности браузера и минимальное количество JavaScript. Такой подход сохраняет философию фреймворка: тонкий слой поведения поверх HTML с максимальной предсказуемостью и контролем.