AdonisJS Inertia Adapter

repository·5.x·Indexed 41 days ago

https://github.com/adonisjs/inertia

The official Inertia.js adapter for the AdonisJS framework, enabling the creation of single-page applications using server-side routing with frontend frameworks like React, Vue, or Svelte. Version 5.0.1 targets the Inertia v3 line and is compatible with @inertiajs/core ^3.4.0. It features a specialized InertiaSerializer for consistent prop serialization via @adonisjs/core transformers, a lazy shared-state provider system via BaseInertiaMiddleware, and asset versioning based on Vite manifest hashes.

Tokens
31.6K
Snippets
73
Records
140
Agent score
100%

What's inside AdonisJS Inertia

  1. Overview of @adonisjs/inertia architectural decisions

    5.x

    The @adonisjs/inertia repository maintains Architecture Decision Records (ADRs) to document the design choices made for the official Inertia.js adapter for AdonisJS. These records explain the context, options considered, and the consequences of specific architectural decisions.

    While ADRs are primarily intended for core maintainers, they provide insight into how the adapter handles core features like:

    • Prop Management: Symbol-tagged prop wrappers, lazy shared-state providers, and top-level-only prop resolution.
    • Middleware & State: Shared props via middleware, per-request Inertia instances, and redirect/version-mismatch semantics.
    • Rendering & Assets: Edge as the root-view layer, asset versioning via Vite manifest hashes, and SSR support.
    • Data & Types: Serialization via @adonisjs/core transformers, page-prop type safety via module augmentation, and multiple errors per field.
    • Advanced Features: Infinite scroll, first-class flash messages, and rescued deferred props.
  2. Introduction to @adonisjs/inertia

    5.x
    The @adonisjs/inertia package is the official adapter for Inertia.js within the AdonisJS framework. It allows you to build single-page apps using classic server-side routing and controllers, while using modern frontend frameworks like React, Vue.js, or Svelte for the view layer.
  3. Reset cached arrays using `X-Inertia-Reset`

    5.x

    The X-Inertia-Reset header is a request header sent from the client to the server. It is used to tell the server which cached prop arrays should be discarded (cleared) before the server processes the request and returns new data.

    This is typically triggered by the client's reset: [...] visit option. The header contains a comma-separated list of prop paths. When the server receives this, it knows those specific props should be treated as fresh/empty rather than merged with existing client-side state.

  4. How Infinite Scroll works in Inertia

    5.x

    Infinite scroll in Inertia is built on top of keyed and directional merging. It allows for continuously loading paginated data as a user scrolls.

    The Workflow:

    1. Client Detection: The client detects when a user reaches a sentinel (the end or start of a list).
    2. Request: The client issues a partial-reload request. It includes a specific header (X-Inertia-Infinite-Scroll-Merge-Intent) to tell the server whether it is loading more items (append) or previous items (prepend), and includes the required page identifier (e.g., ?page=2).
    3. Server Response: The server provides the new paginated data and a scrollProps object containing pagination metadata (current, next, and previous page identifiers).
    4. Merging: The client merges the new data into the existing list based on the direction and uses matchPropsOn (if provided) to deduplicate items that might overlap between adjacent pages.
    5. Resetting: If the server sends reset: true in the scrollProps for a specific prop, the client discards all cached items for that prop before merging the new data (useful for when filters change).
  5. How Inertia DevTools works

    5.x

    Inertia DevTools uses a server-side recorder to capture Inertia request/response cycles and store them as JSON files on disk. This allows a browser extension to fetch and display a timeline of your application's state, props, and network activity.

    Core Workflow:

    1. Capture: When an Inertia request is processed, a recorder collects metadata from the Inertia#page() (for prop types) and Inertia#render() (for component and values) methods.
    2. Storage: Entries are written atomically to a file-based store. A _meta.json index is maintained to allow the DevTools panel to list entries efficiently without reading every file.
    3. Redaction: Before writing to disk, sensitive data is scrubbed from the payload, query parameters, and headers based on your configuration.
    4. Discovery: The server injects a discovery tag into the HTML response (replacing the last </body> tag) to signal to the browser extension that an Inertia page has been rendered.
    5. Access: The DevTools panel communicates with the server via specific endpoints. Access is protected by the authorize callback you define in your configuration.
  6. How to detect prefetch requests on the server

    5.x

    Inertia clients may issue prefetch requests to warm the local cache before a user navigates. These requests are identified by the standard HTTP Purpose header.

    When building application handlers, you should detect this header to avoid executing one-shot side effects (such as analytics, view counters, or database writes) that should only occur during an actual user navigation. The @adonisjs/inertia adapter is designed to expose this prefetch state to your application code.

    Purpose: prefetch
  7. Understand the top-level-only prop resolution rule

    5.x

    In the @adonisjs/inertia adapter, prop wrappers (such as optional, defer, merge, and once) are resolved only at the top level of the props object.

    Unlike the Laravel adapter, this package does not support resolving wrappers nested inside closures, nested arrays, or objects. If you need to use a wrapper, you must lift it to a top-level property of the props object.

    Key constraints:

    • No Nested Resolution: Wrappers placed inside plain objects or nested structures are passed through serialization untouched and will not be resolved.
    • No Dot-Notation Paths: Partial reload headers (e.g., X-Inertia-Partial-Data) and merge metadata only support top-level keys. You cannot use dot-notation (like user.profile) to target nested data for partial reloads.
    • Type Safety: This design aligns with the module-augmentation typing model, which maps top-level prop keys to component props. Nested wrappers would be invisible to these generated types.
  8. Precognition support is not supported in @adonisjs/inertia

    5.x

    The @adonisjs/inertia adapter does not implement support for Inertia Precognition headers (such as Precognition or Precognition-Validate-Only).

    Requests carrying these headers are processed as ordinary Inertia or plain HTTP requests. The adapter will not intercept these requests to perform validation-only "dry-runs" without executing the handler's side effects.

    If you are porting an application from Laravel that relies on Precognition for live field-level validation, you must implement validation as an explicit endpoint in your AdonisJS application. This can be consumed by the Inertia client using the useHttp hook.

  9. How Inertia prop wrappers work and compose

    5.x

    Inertia uses factory functions to define how specific props should be evaluated and sent to the client. These wrappers allow you to specify behaviors like deferring a request, making a prop optional, or defining how the client should merge data.

    Key Behaviors

    • Deferring: Props that are deferred to a follow-up request.
    • Optionality: Props that are only sent when explicitly requested.
    • Presence: Props that must always be present.
    • Merging: Props that the client should merge into existing state rather than replacing them (supports both shallow merge() and deepMerge()).

    Composition and Spread-Safety

    Wrappers are implemented as plain object literals tagged with private Symbols. This design ensures that:

    1. Composition works: You can chain behaviors, such as defer(...).merge().
    2. Spread-safety: Because they are plain objects, they survive object spreading (e.g., { ...sharedProps, ...pageProps }) without losing their identity.
    3. Serialization safety: The internal symbols used for detection are never serialized and do not leak into the JSON payload sent to the client.
  10. How shared props are resolved and prioritized

    5.x

    Inertia uses a lazy, ordered provider system for shared props. Shared props can be registered via middleware share() or via inertia.share() calls within a controller.

    Resolution Lifecycle:

    1. Registration: Calling share() appends a provider (either a plain object or an AsyncOrSync function) to an internal list. This is lazy; no computation occurs during registration.
    2. Resolution: Providers are only resolved when #buildPageProps is called (at page-build time). All functions are invoked concurrently using Promise.all.
    3. Merging & Precedence: Providers are reduced from left-to-right into a single object. The final shared state is then merged with page-specific props using a spread operation: { ...sharedState, ...pageProps }.

    Precedence Rules:

    • Page Props > Shared Props: If a key exists in both, the page-specific prop wins.
    • Later Shared Props > Earlier Shared Props: If multiple share() calls provide the same key, the one registered last wins.

    Performance Note: Because resolution is lazy, requests that do not render an Inertia page incur zero cost for shared prop computation. However, providers are re-resolved on every render; there is no memoization across multiple renders within a single request.

  11. How Rescued Deferred Props work

    5.x

    A deferred prop's resolution can fail due to database errors, timeouts, or exceptions. Without handling, the client remains in a perpetual loading state.

    Rescued deferred props allow for graceful failure. When a deferred prop marked with rescue: true fails to resolve, the server catches the error, omits the prop from the response, and adds its path to a rescuedProps list. This signals the client to render a fallback (rescue) state instead of a loading indicator.

    Key Behaviors

    • Opt-in: Rescue must be explicitly enabled per deferred prop using rescue: true (e.g., Inertia::defer(..., rescue: true)).
    • Omission: If a prop fails, it is omitted from the props object entirely (it is not sent as null).
    • Client State: The client uses the rescuedProps array to determine which <Deferred> components should render their rescue slot. The rescue state persists until the prop successfully reloads via a partial reload.

    Note: This feature requires Inertia v3. It is not compatible with Inertia v2.x clients.

  12. How multiple errors per field work in the Inertia protocol

    5.x

    The Inertia protocol allows validation errors to carry multiple messages for a single field. However, the shape of the errors object is uniform across the entire response. You cannot mix strings and arrays in a single response; you must choose one mode for the whole payload.

    Error Modes

    1. Default Mode: Every field in the errors object is a single string (representing the first error message).
    2. All-messages Mode: Every field in the errors object is a string[]. Even if a field has only one error, it is delivered as a one-element array (e.g., ['Error message']).

    Wire Format Examples

    Default mode (single strings):

    { "errors": { "email": "Email is required", "password": "Must be at least 8 characters" } }

    All-messages mode (arrays for every field):

    {
      "errors": {
        "email": ["Email is required"],
        "password": ["Must be at least 8 characters", "Must contain a number"]
      }
    }

    When using an Error Bag (via the X-Inertia-Error-Bag header), the same rule applies: the shape is uniform under the bag's key.

    { "errors": { "login": { "email": ["Email is required"] } } }