Laravel + React Starter Kit: Docker-verified fork
We took the official laravel/react-starter-kit (Inertia + React 19 + shadcn/ui + Fortify), ran it inside Docker Desktop, and published a production-hardened fork. The test suite goes from the upstream 40 passed to 44 passed / 151 assertion…
Verification environment
- PHP 8.5.5
- Laravel 13.5.0
- Composer 2.9.7
- Node 22.22.2
- npm 10.9.7
- Database SQLite (tests)
- OS Docker Desktop (php:8.5-cli-bookworm)
Laravel + React Starter Kit: Docker-verified fork
We took the official laravel/react-starter-kit (Inertia + React 19 + shadcn/ui + Fortify), ran it inside Docker Desktop, and published a production-hardened fork. The test suite goes from the upstream 40 passed to 44 passed / 151 assertions. This article covers the React / Inertia-specific findings only.
The backend improvements (.env.example, timezone, security headers, HTTPS forcing, rate limiting, auth logging) are identical across the React, Vue, and Livewire forks, so they're consolidated in the pillar article:
→ Hardening the shared Laravel starter-kit backend
This article is the cluster piece: it covers only what's specific to the React Starter Kit.
Target
| Item | Value |
|---|---|
| Name | Laravel + React Starter Kit |
| Official URL | https://github.com/laravel/react-starter-kit |
| Improved fork | https://github.com/codelift-dev/react-starter-kit/tree/improvements |
| Stack | Laravel 13 + Inertia + React 19 + TypeScript + shadcn/ui + Fortify |
| Upstream / improved license | MIT / MIT |
| Verification date | 2026-04-19 |
| Upstream commit | laravel/react-starter-kit@2e22614 |
Environment
Same as the pillar article (Docker Desktop, PHP 8.5.5 / Laravel 13.x / Node 22.22.2). Reproduce via the fork's codelift/ files:
git clone https://github.com/codelift-dev/react-starter-kit.git
cd react-starter-kit
git checkout improvements
docker compose -f codelift/docker-compose.yml build
docker compose -f codelift/docker-compose.yml run --rm app
React-specific findings
A. npm run build before composer install fails opaquely
Specific to the React / Vue kits (the Livewire kit doesn't have this).
Symptom: on a fresh clone, running npm run build first fails the Vite build with:
require(.../vendor/autoload.php): Failed to open stream: No such file or directory
in artisan on line 10
Cause: the @laravel/vite-plugin-wayfinder plugin in vite.config.ts shells out to php artisan wayfinder:generate during the build. Wayfinder generates typed TypeScript route helpers from Laravel's route definitions, which means booting artisan at build time. Without Composer dependencies, artisan can't load vendor/autoload.php and dies.
Why it's nasty: the error never mentions composer install. A first-time contributor suspects a Vite or Wayfinder bug and burns time in the wrong direction. Only someone fluent in Laravel reads vendor/autoload.php and immediately thinks "Composer hasn't run yet."
Fix: a README Setup section spelling out the composer install → npm run build order, plus a note that Wayfinder calls artisan at build time so the reason for the ordering is clear.
React 19 + Inertia initial state and CSP
The React Starter Kit uses React 19. Inertia bridges server-side routing and React components: each navigation returns a JSON "page object" the server produces and React renders.
The catch is the first load. Inertia must embed the page component and initial props into the initial HTML. That goes into a root-element attribute — <div id="app" data-page="{...encoded props...}"> — emitted by the @inertia Blade directive.
This "embed initial props directly in HTML" design is the obstacle when tightening Content-Security-Policy. To drop 'unsafe-inline' entirely and move to a nonce-based policy, you'd have to reach into Inertia's response rendering (the HandleInertiaRequests middleware or Inertia's response class) and thread a nonce through.
CodeLift's fork introduces a CSP (pillar finding D) but keeps 'unsafe-inline' in script-src as a conservative setting. The nonce migration involves modifying Inertia and is a separate topic for a follow-up.
For contrast, the Livewire kit — which doesn't use Inertia — does not embed initial state in an inline script, so it can move to a nonce-based CSP without 'unsafe-inline'. That technique is detailed in the Livewire CSP nonce article.
J. Settings endpoints — only password update is throttled
The React Starter Kit's routes/settings.php attaches an explicit throttle only to the password update route.
Route::patch('settings/profile', ...)->name('profile.update'); // no throttle
Route::delete('settings/profile', ...)->name('profile.destroy'); // no throttle
Route::put('settings/password', ...)->middleware('throttle:6,1')...; // throttled
On a compromised session, repeated profile edits or account-deletion attempts run under the global default.
Fix: in the React/Vue kits, settings mutations are plain PATCH / DELETE HTTP verbs, so adding throttle middleware at the route definition is enough.
- Route::patch('settings/profile', ...)->name('profile.update');
+ Route::patch('settings/profile', ...)->middleware('throttle:10,1')->name('profile.update');
- Route::delete('settings/profile', ...)->name('profile.destroy');
+ Route::delete('settings/profile', ...)->middleware('throttle:3,1')->name('profile.destroy');
Account deletion is terminal, so it's tightened to 3/minute. (The Livewire kit can't reuse this fix as-is because it routes through Route::livewire component AJAX — see the Livewire article.)
Commit layout of the improved fork
8 commits on the improvements branch:
| Commit | Finding | Source |
|---|---|---|
| README Setup | A (React/Vue-specific) | this article |
.env.example comments |
B | pillar |
| timezone via env | C | pillar |
URL::forceScheme |
E | pillar |
SetSecurityHeaders middleware + test |
D | pillar |
| layered login rate limiter | G | pillar |
auth log channel + subscriber + test |
I | pillar |
| settings throttle | J (this article) | this article |
Tests: upstream 40 passed → improved 44 passed / 151 assertions (SecurityHeaders +2, AuthLogging +2, all existing still green).
Before / after (React-specific part)
| Dimension | Official | Improved |
|---|---|---|
php artisan test |
40 passed / 136 assertions | 44 passed / 151 assertions |
| Build-order documentation | None (Wayfinder footgun) | README Setup section |
| Settings endpoint throttle | Password only | + profile update / destroy |
The before/after for the 6 shared backend findings is in the pillar article.
When this fits
- You're starting a production product on
laravel/react-starter-kitand want the floor set first. - You're choosing between React / Vue / Livewire and want the React production cost. → The verdict is in the three-way comparison (backend cost is identical across all three).
Reproduce and adopt
git clone https://github.com/codelift-dev/react-starter-kit.git
cd react-starter-kit
git diff main improvements -- . ':!codelift'
Each commit is standalone, cherry-pickable.
Related
- Pillar: Hardening the shared Laravel starter-kit backend
- Sister clusters: Vue / Livewire
- Comparison: React vs Vue vs Livewire
License
Upstream and improved fork both MIT. Findings reflect the verification date.
Featured in comparisons
Related articles
- Laravel + Vue Starter Kit: Docker-verified fork We took the official laravel/vue-starter-kit (Inertia + Vue 3 + shadcn-vue + Fortify), ran it inside Docker, and published a production-hardened fork. Tests go from 40 passed to 44 passed / 151 assertions. This article covers the Vue / Ine…
- Laravel + Livewire Starter Kit: Docker-verified fork We took the official laravel/livewire-starter-kit (Livewire v4 + Flux + Alpine), ran it inside Docker, and published a production-hardened fork. Tests go from the upstream 33 passed to 37 passed / 92 assertions. This article covers the Liv…
- Hardening the shared Laravel starter-kit backend Laravel's official starter kits (React / Vue / Livewire) differ in their frontend layer, but they share the same Laravel + Fortify backend code. So most of the production-hardening work is identical across all three. This is the pillar art…