Building Type-Safe Inertia Apps with React and TypeScript

Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

February 13, 2025 · 3 min read

Building Type-Safe Inertia Apps with React and TypeScript

If you’ve ever worked on a full-stack application, you know how tricky it can be to keep your frontend and backend in perfect sync. Type mismatches can sneak up on you, causing frustrating bugs that only appear at runtime. What if you could eliminate manual type duplication and catch data mismatches early in development?

Setup Inertia with Laravel, React, and TypeScript

This powerful setup combines the robust backend capabilities of Laravel with the flexible frontend of React, all while maintaining strict TypeScript safety. By automatically generating TypeScript types from your PHP Data objects, you get a seamless development experience where everything from type checking to IDE autocompletion works across your entire stack.

In this post, we’ll walk through a step-by-step guide to implementing this modern setup. You’ll learn how to:

  • Eliminate manual type duplication

  • Keep your frontend and backend contracts in sync

  • Catch data mismatches at build time

  • Enable IDE autocompletion across your full-stack

This approach has revolutionized how we build and maintain our applications, significantly reducing cross-stack bugs while accelerating feature development.

Step 1: Using Laravel Data Classes for Type Safety

Install Required Packages

Let’s start by installing the necessary packages. We’ll need both the Laravel Data package and the TypeScript transformer:

composer require spatie/laravel-data spatie/laravel-typescript-transformer

Create Data Objects

Here’s how to create your Data objects. These will be the foundation of your type-safe data structure:

<?php

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript] // Magic happens here
class TeamData extends Data
{
    public function __construct(
        public readonly string $id,
        public readonly string $name,
        public readonly ?TeamThemeData $theme,
        public readonly bool $personal_team,
    ) {}
}

Key points:

  • Use #[TypeScript] attribute to enable auto-generation

  • TypeScript-visible properties must be in the constructor

  • Supports nested Data objects (like TeamThemeData)

Step 2: Automatically Generating TypeScript Types

Configure Vite for Type Generation

Let’s set up Vite to automatically generate TypeScript types whenever we make changes to the app/Data folder. First start by installing vite-plugin-run :

npm install -D vite-plugin-run

Now let’s handle the configuration for Vite:

import react from '@vitejs/plugin-react'
import laravel from 'laravel-vite-plugin'
import { defineConfig } from 'vite'
import { run } from 'vite-plugin-run'

export default defineConfig({
  plugins: [
    laravel({
      input: ['resources/js/app.tsx', 'resources/css/filament/admin/theme.css'],
      refresh: true,
    }),
    react(),
    run([
      {
        name: 'typescript transform',
        run: ['php', 'artisan', 'typescript:transform'],
        pattern: ['app/**/*Data.php'],
      },
    ]),
  ],
})

This setup:

  • Watches all Data classes

  • Auto-runs type generation on changes

  • Works seamlessly with HMR

Generate Initial Types

Once everything is set up, run this command to generate your initial types:

php artisan typescript:transform

Generated files appear in resources/js/types/generated.d.ts

Step 3: Type-Safe Controllers in Laravel

Here’s a practical example of how to use type-safe controllers in Laravel:

<?php

namespace App\Http\Controllers;

use Inertia\Inertia;
use App\Data\UserData;
use App\Data\TeamData;

class UserProfileController
{
	public function show()
	{
	  return Inertia::render('profile/edit', [
		  'user' => UserData::from(auth()->user()), // Automatic type conversion
		  'teams' => TeamData::collect((auth()->user()->teams()->get())
	  ]);
	}
}

Step 4: Integrating Generated Types with React

Now let’s see how to use these generated types in your React app with Inertia:

export default function UserProfile({
  user,
  teams,
}: {
  user: App.Data.UserData
  teams: App.Data.TeamData[]
}) {

  return (
	<>... rest of the code here </>
  )
}

Common Pitfalls & Solutions

Here are some common issues you might run into and how to solve them:

🚧 Missing Types

  • Ensure #[TypeScript] attribute is present

  • Check file is in app/**/*Data.php pattern

  • Run php artisan typescript:transform

🚧 Type Mismatches

  • Verify PHP DocBlocks match property types

  • Check for null vs undefined differences

  • Use /** @var Type[] $prop */ for arrays

🚧 Slow Compilation

  • Add a .gitignore entry for generated types

  • Use the --no-interaction flag in CI/CD

  • Implement incremental type generation

Conclusion

By implementing this type-safe Inertia setup with Laravel, React, and TypeScript, you’re not only reducing cross-stack bugs but also unlocking a faster, more enjoyable development experience. The automatic generation of TypeScript types from Laravel Data objects eliminates manual type duplication and ensures your frontend and backend remain in perfect sync. With features like IDE autocompletion and build-time type checking, you can catch issues early and deliver robust, maintainable applications.


Bring Your Ideas to Life 🚀

If you need help with a Laravel project, let’s get in touch.

Lucky Media is proud to be recognized as a leading Laravel Development Agency.

Technologies

LaravelReactInertia
Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

Founder and CEO of Lucky Media

Stay up-to-date

Be updated with all news, products and tips we share!

Let’s chat

We partner with a limited number of brands each quarter to ensure senior-level attention on every project.

lokman and arlind headshots
Teamwork