Skip to content

Add Inertia.js integration for Nette Framework #9

@ohmyfelix

Description

@ohmyfelix

Background

We want to add reusable Inertia.js support to contributte/ui for Nette applications.

Related references:

Goal

Implement a reusable, protocol-correct Inertia integration in contributte/ui, while keeping frontend runtime setup in app/skeleton land.

The package should handle the server-side Inertia protocol and Nette integration, but stay frontend-agnostic.

Key Principles

  • Keep contributte/ui frontend-agnostic
  • Do not embed Vue/React/Svelte-specific runtime setup into the library
  • Follow Inertia protocol exactly
  • Prefer a trait over an abstract presenter
  • Keep the root template app-specific
  • Resolve lazy props after partial filtering
  • Treat errors as a first-class shared prop

Proposed Scope

New module

Add a new isolated module under src/Inertia:

  • InertiaPage (page payload object)
  • Inertia (core service)
  • InertiaErrorStore (session-backed error storage)
  • InertiaLocationResponse (409 + X-Inertia-Location)
  • TInertiaPresenter (presenter integration)
  • InertiaLatteExtension
  • InertiaNode
  • DI/InertiaExtension

Config

Proposed config:

extensions:
	inertia: Contributte\UI\DI\InertiaExtension

inertia:
	rootTemplate: %appDir%/UI/@Templates/@inertia.latte
	rootId: app
	manifest: %wwwDir%/dist/manifest.json
	# version: '...'

Protocol-Correct v1

Implement these behaviors:

  • Initial request returns HTML with root <div data-page="...">
  • Inertia request (X-Inertia: true) returns JSON page object
  • JSON responses include X-Inertia: true
  • Responses use Vary: X-Inertia
  • GET version mismatch returns 409 with X-Inertia-Location
  • Redirects after non-GET Inertia requests use 303
  • Partial reloads support:
    • X-Inertia-Partial-Component
    • X-Inertia-Partial-Data
    • X-Inertia-Partial-Except
  • errors prop is always present, defaulting to empty

Package Shape

InertiaPage

Immutable page payload object implementing JsonSerializable.

Fields:

  • component
  • props
  • url
  • version
  • clearHistory
  • encryptHistory

Inertia

Responsibilities:

  • shared props
  • request detection
  • version resolution
  • partial reload filtering
  • lazy prop resolution
  • page creation

Use relative page URL from the HTTP request.

InertiaErrorStore

Session-backed error storage with support for error bags via X-Inertia-Error-Bag.

This keeps the package independent from nette/forms for v1.

TInertiaPresenter

Provide:

  • inertia(string $component, array $props = []): void
  • inertiaShare(array|string $key, mixed $value = null): void
  • inertiaErrors(array $errors, ?string $bag = null): void
  • inertiaLocation(string $url): void

Trait should also normalize redirects to 303 for non-GET Inertia requests.

Latte integration

Add:

  • {inertia}
  • {inertiaHead}

{inertiaHead} can stay empty/placeholder in v1 for future SSR support.

DI/InertiaExtension

Real DI extension that wires:

  • services
  • config schema
  • Latte extension registration

This should be closer to contributte/vite's integration style than the current schema-only DI\ViteExtension.

Important Differences From ui-skeleton PR 20

The skeleton PR is a good kick-off, but the reusable package should avoid these issues:

  • use Vary: X-Inertia instead of Vary: Accept
  • use relative page url, not absolute
  • do not hardcode frontend framework selection in PHP config
  • do not resolve all closures before partial filtering
  • avoid brittle presenter-relative root template path guessing
  • keep demo/frontend bootstrap code in ui-skeleton, not contributte/ui

Suggested Implementation Steps

  1. Update composer.json

    • add nette/http
    • keep nette/forms out of v1
  2. Implement src/Inertia/*

    • page object
    • core service
    • location response
    • error store
    • presenter trait
    • Latte extension + node
  3. Implement src/DI/InertiaExtension.php

    • config schema
    • service wiring
    • Latte registration
  4. Add tests under tests/Cases/Inertia

    • page serialization
    • partial reload filtering
    • lazy closure behavior
    • error bag loading
    • version conflict handling
    • Latte output
    • redirect normalization
  5. Update docs in .docs/README.md

    • install
    • config
    • presenter usage
    • root template example
    • Vite usage with existing bundler integration
  6. Follow up in contributte/ui-skeleton

    • register extension
    • add root template
    • wire trait into BasePresenter
    • add frontend bootstrap and example page there

Recommended v1 / v2 Split

v1

  • page object
  • presenter trait
  • Latte tags
  • DI extension
  • manifest/version support
  • partial reload support
  • session-backed errors
  • 303 redirect normalization

v2 / later

  • SSR transport/head integration
  • deferred props
  • once props
  • merge props
  • infinite scroll metadata
  • richer integrations around forms/validation

Sketch

final class Inertia
{
	/** @var array<string, mixed> */
	private array $shared = [];

	public function share(array|string $key, mixed $value = null): void
	{
		if (is_array($key)) {
			$this->shared = array_merge($this->shared, $key);
			return;
		}

		$this->shared[$key] = $value;
	}
}
trait TInertiaPresenter
{
	public function inertia(string $component, array $props = []): void
	{
		// set Vary: X-Inertia
		// handle version mismatch
		// return JSON for Inertia requests
		// render root template for initial request
	}
}

Acceptance Criteria

  • contributte/ui provides reusable Inertia server-side integration for Nette
  • the implementation is protocol-correct for the v1 scope above
  • the library remains frontend-agnostic
  • the skeleton can consume the package cleanly without package-level demo code
  • behavior is covered by .phpt tests
  • docs show a minimal complete setup

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions