React/Inertia Rendering (Optional)
Public Wrangler pages render via Blade/Livewire by default. This package also ships an optional Inertia/React rendering mode for public pages — the admin panel stays Blade/Livewire either way; this only swaps how visitors see public pages rendered.
Pick one per site. Both modes live in the same package and read the same WranglerPage/
WranglerComponent data — nothing about your content or admin workflow changes.
Prerequisites
An Inertia + React frontend. Jetstream's own "Jetstream with Inertia" option only ever
installs a Vue-based Inertia stack (@inertiajs/vue3) — it doesn't offer React,
so this isn't something jetstream:install sets up. Instead:
- New app: start with Laravel's official React starter kit
(
laravel new your-app --react, orcomposer create-project laravel/react-starter-kit) before adding this package. - Existing app: add Inertia/React by hand —
@inertiajs/react,react,react-dom, and@vitejs/plugin-reactinpackage.json,inertiajs/inertia-laravelvia Composer, and the React plugin wired intovite.config.js. See Inertia's own React setup guide — there's no artisan command that does this the wayjetstream:installdoes for Vue. laravel/fortify— used for a role-aware post-login redirect; already present via either path above.
Install
php artisan textstem:install-react
This publishes the React source (under resources/js/) and, if you confirm, appends
TEXTSTEM_REACT_RENDERING=true to your .env. Two things it can't do for you, since they
touch files you own and customize:
- In
resources/js/app.tsx, route pages namedtextstem/*to the published layout:import PublicLayout from '@/layouts/public-layout'; // ... layout: (name) => { if (name.startsWith('textstem/')) { return PublicLayout; } // ... your existing cases }, - In your own
resources/js/types/global.d.ts, add thetextstemprop to yoursharedPagePropsdeclaration (TypeScript doesn't merge two separate inline shapes assigned to the same property across files, so this can't be a standalone augmentation file the waywrangler.ts's other types are):import type { TextstemSharedProps } from '@/types/textstem-shared'; // ... declare module '@inertiajs/core' { export interface InertiaConfig { sharedPageProps: { // ... your existing keys textstem: TextstemSharedProps; [key: string]: unknown; }; } } - Confirm
@inertiajs/react,react, andreact-domare inpackage.json(see Prerequisites above), thennpm run build(ornpm run dev).
What gets published
Under resources/js/:
wrangler/registry.tsx,PageSlot.tsx,Container.tsx,ComponentBoundary.tsx,MissingComponent.tsx,AdminBar.tsx— the rendering engine. You shouldn't need to touch these.wrangler/components/Prose.tsx(+index.ts) — the one component this package ships a Blade view for (src/Wrangler/Components/Prose.php). Add your own the same way you would for Blade: createApp\Wrangler\Components\{Name}(PHP) and a matchingresources/js/wrangler/components/{Name}.tsx, thenregisterWranglerComponent('name', Name)incomponents/index.ts. See Notes-wranglercomponents.md for the PHP side — thebuild()method (notrender()) is what the React path calls;ActiveComponent::build()already has a safe default (returns the component's raw config as data) if you don't override it, same as any component without an explicitbuild()today.wrangler/page-templates/DefaultTemplate.tsx(+registry.tsx,index.ts) — the one page template this package ships a Blade view for. Add your own the same way: register the template name yourWranglerPage::templatecolumn uses.layouts/public-layout.tsx,pages/textstem/page.tsx— the page shell and the actual Inertia page componentReactPageControllerrenders (Inertia::render('textstem/page', ...)).types/wrangler.ts— TypeScript types for page/component data.types/textstem-shared.d.ts— exportsTextstemSharedProps, the shape of thetextstemprop (see below). You add this to your ownglobal.d.tsby hand (step 2 above) rather than it being a drop-in augmentation — see that file's own header comment for why.
How it works
ReactPageController(bound overWranglerPageControllerwhilereact_renderingis on) replaces the publicpublish()/notfound()rendering path withInertia::render('textstem/page', ...), built fromPageRenderer::renderPage(..., asArray: true)— the same headless data path the admin's live-preview/skeleton views already use, not a new rendering mode of its own.- A lightweight page-props cache sits in front of that (
textstemapp.cache.*— the same config keys the Blade path's cache settings use), keyed on path + query, skipped for authenticated visitors and non-public-access pages. This is a separate store from the package's ownPageCache(a raw-HTML file cache with no equivalent shape for an Inertia props array); the admin's "Clear Cache" action'sCache::flush()fallback clears both. - A
textstemprop is shared on every Inertia page (TextstemServiceProvider::boot(), independent ofreact_rendering, since a host app may want it on its own Inertia pages too):canAccessAdmin,dashboardUrl/loginUrl/logoutUrl(resolved server-side, not with a client route() helper, so the published layout doesn't need Wayfinder or Ziggy), andadminBar(the floating "Dashboard / Edit Page / Show-Hide Editables" overlay, admin-only, mirroring the package's own<x-textstem::adminbar/>Blade component but reading from this shared prop so it stays correct across client-side Inertia navigation instead of only updating on a full page load). - Fortify's
LoginResponse/RegisterResponse/TwoFactorLoginResponseare bound to role-aware versions whilereact_renderingis on (only iflaravel/fortifyis present): a visitor who canaccess-textstem-adminlands on yourdashboardroute after login, everyone else lands on/, rather than everyone being sent toconfig('fortify.home')regardless of role.
Configuration
config/textstemapp.php:
'react_rendering' => env('TEXTSTEM_REACT_RENDERING', false),
Everything else (cache, admin_roles, inline-component-editor) reuses the existing
config keys documented elsewhere in this package — nothing new to configure there.