Zenn
👻

laravel12 スターターキット (react)最速レビュー

2025/02/25に公開
1

デモ(ある程度日本語になっていますが): https://starter.gozapon.net/

本スターターキットを理解するにあたって全体的に抑えておいた方がよさそうな事

  • このキットではbladeは最早使えない(重要)
  • react, vue3, livewireの3つが使えると謳ってはいるが実態は以下の2つに大別できるはず
    • livewire
    • inertia.js を使ったreactあるいはvue
  • すなわち、reactやらvueといってもフロントエンド/バックエンド分離型アプリケーションのキットは一切提供していない

starter kits

starter kitsを使う場合は laravelコマンドからプロジェクトを作成する必要がある。即ち1からプロジェクトを初めるというパターン以外なかなかこれを適用するのは難しいため、新たな案件がないと使うのは難しいかもしれない。
しかし新たに何か作ってみようという場合これを検討してみる価値は結構ある。

composer global require laravel/installer


スターターキットの選択。bladeは消滅


WorkOSという認証システムを選べるようになっている。筆者は使った事がないのでここでは割愛

こんな具合で resources/ にたくさんつまった状態になっている。その殆どがtsxだ。


/ で見られる新しいwelcomeページ。

routes/web.php

routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';

settings.phpを分割してきた。時代の流れはroute分割なのかも?

ログインページ


新しくデザインされたログインページ

これは

resources/js/pages/auth/login.tsx
import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';

import InputError from '@/components/input-error';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import AuthLayout from '@/layouts/auth-layout';

interface LoginForm {
    email: string;
    password: string;
    remember: boolean;
}

interface LoginProps {
    status?: string;
    canResetPassword: boolean;
}

export default function Login({ status, canResetPassword }: LoginProps) {
    const { data, setData, post, processing, errors, reset } = useForm<LoginForm>({
        email: '',
        password: '',
        remember: false,
    });

    const submit: FormEventHandler = (e) => {
        e.preventDefault();
        post(route('login'), {
            onFinish: () => reset('password'),
        });
    };

    return (
        <AuthLayout title="Log in to your account" description="Enter your email and password below to log in">
            <Head title="Log in" />
// ...

とまあ基本的に全てtypescriptになっている。もちろんこれはInertia.jsで書かれている。

あとPages/pages/ と、全て小文字になっている。細かい所だけどキーボードで補完マンだと結構戸惑ったりして...

フォームコンポーネント

これは基本的にbreezeのものを変化がないのでそれを使っていた人なら問題なく移行できるはず

ボタンコンポーネントなど

breezeではPrimaryButtonとかでコンポーネントしていたものは結構変化がある

resources/js/components/ui/button.tsx
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';

import { cn } from '@/lib/utils';

const buttonVariants = cva(
    'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
    {
        variants: {
            variant: {
                default: 'bg-primary text-primary-foreground hover:bg-primary/90',
                destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
                outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
                secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
                ghost: 'hover:bg-accent hover:text-accent-foreground',
                link: 'text-primary underline-offset-4 hover:underline',
            },
            size: {
                default: 'h-10 px-4 py-2',
                sm: 'h-9 rounded-md px-3',
                lg: 'h-11 rounded-md px-8',
                icon: 'h-10 w-10',
            },
        },
        defaultVariants: {
            variant: 'default',
            size: 'default',
        },
    },
);

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
    asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';
    return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
});
Button.displayName = 'Button';

export { Button, buttonVariants };
  • default: 'bg-primary text-primary-foreground hover:bg-primary/90',
  • destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
  • outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
  • secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
  • ghost: 'hover:bg-accent hover:text-accent-foreground',
  • link: 'text-primary underline-offset-4 hover:underline',

このように定義された。つまりこれに bg- プレフィックスが付いている。

この辺のコンポーネントの定義はある程度理解しておかないと無駄にコード量を増やす事になってしまいそうではある。

色の管理

resources/css/app.css でやってるようだ

    --primary: hsl(0, 0%, 9%);
    --primary-foreground: hsl(0, 0%, 98%);
    --secondary: hsl(0, 0%, 96.1%);
    --secondary-foreground: hsl(0, 0%, 9%);
    --muted: hsl(0, 0%, 96.1%);
    --muted-foreground: hsl(0, 0%, 45.1%);
    --accent: hsl(0, 0%, 96.1%);
    --accent-foreground: hsl(0, 0%, 9%);
    --destructive: hsl(0, 84.2%, 60.2%);
    --destructive-foreground: hsl(0, 0%, 98%);

ボタンコンポーネント

<Button variant="destructive" disabled={processing} asChild>
  <button type="submit">Delete account</button>
</Button>

となっているが、実はbuttonは

<Button variant="destructive" type="submit">Delete account</Button>

でも書ける、でもLinkにしたいなら

<Button variant="destructive" asChild>
    <Link route={profile.destroy}>Delete account</Link>
</Button>

このようにするという事もできるという事であり、やはり基本的には少なくとも resources/js/components/ui/ 以下のコンポーネントはちょっと読んでドキュメントする必要性を感じざるを得なかった。

ダッシュボード

これは量が少ないので全文を載せる

resources/js/pages/dashboard.tsx
import { PlaceholderPattern } from '@/components/ui/placeholder-pattern';
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head } from '@inertiajs/react';

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Dashboard',
        href: '/dashboard',
    },
];

export default function Dashboard() {
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Dashboard" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                <div className="grid auto-rows-min gap-4 md:grid-cols-3">
                    <div className="border-sidebar-border/70 dark:border-sidebar-border relative aspect-video overflow-hidden rounded-xl border">
                        <PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
                    </div>
                    <div className="border-sidebar-border/70 dark:border-sidebar-border relative aspect-video overflow-hidden rounded-xl border">
                        <PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
                    </div>
                    <div className="border-sidebar-border/70 dark:border-sidebar-border relative aspect-video overflow-hidden rounded-xl border">
                        <PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
                    </div>
                </div>
                <div className="border-sidebar-border/70 dark:border-sidebar-border relative min-h-[100vh] flex-1 rounded-xl border md:min-h-min">
                    <PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
                </div>
            </div>
        </AppLayout>
    );
}

Profileの変更など基本的な機能はLaravel Breezeとほとんど変更がない。なお、リポジトリーやドキュメントへのリンクは外部リンクであるので、これは単純にデザインを整えるために配置されているだけと思ってよい。

BreadcrumbItemというのをソースに直接で定義していてこれに従うと増減があるとちょっと面倒っぽいのでこれは別の仕組みを考えるべきと思うが、デモとしてはこれでいいとも思う。

profile

基本機能としてダークモードがついている

レイアウト

resources/js/layouts/
resources/js/layouts/app
resources/js/layouts/app/app-header-layout.tsx
resources/js/layouts/app/app-sidebar-layout.tsx
resources/js/layouts/auth
resources/js/layouts/auth/auth-split-layout.tsx
resources/js/layouts/auth/auth-card-layout.tsx
resources/js/layouts/auth/auth-simple-layout.tsx
resources/js/layouts/settings
resources/js/layouts/settings/layout.tsx
resources/js/layouts/app-layout.tsx
resources/js/layouts/auth-layout.tsx

認証レイアウト

  • スプリットレイアウト
  • カードレイアウト
  • シンプルレイアウト

が存在しておりdefaultはシンプルレイアウトだ



スプリットレイアウト



カードレイアウト



シンプルレイアウト


アプリケーションレイアウト

headerレイアウトとsidebarレイアウトの2つが存在している


headerレイアウト



sidebarレイアウト

所感

breezeよりちょっとつっこんだレイアウトやデザインを提供してくれるという感じではあるものの、結局この類はカスタムがかなり必要である。まだ開発を進めていないのでどれくらいこの機構を便利に利用できるかは不明なのだが少なくともメニューリストやらブレッドクラムとかをそのまま使おうとするとコード量が単純に増えて大変なのかなという気はします。

あと基本的にモノクロなのは shadcn/ui のコンセプトに従っているからであり、色の管理はresources/css/app.cssでチューニングすると予想する

さらに、基本的に問答無用でtypescriptになっている。

laravel12にbreaking changesが入っていないため、backendとの連携においてはlaravel11と互換性がある。そのため特に学習が追加で必要となるコードは見られないし、スターターキットもbreezeと比べて機能が特別増えているというでもない。あとはスターターキットからの機能を増やすにせよ減らすにせよユーザーが自由に選択できる余地が残されていると感じる。

また、おそらくlaravel breezeも依然として自分で投入すれば利用可能であるだろうからそれを使うのも手ではある。特にbladeを使おうと思うと、もうstarter kitからは外れているのでそれするしか選択肢が無い。これはlaravel uiが本体から切り離された時の状況にまあまあ似てるといえば似てますね、とはいえlaravel breezeはofficialに組込まれているものではないといえばそうなんですが。
いずれにせよlaravel的にはbladeだけでのデザインはもうしないで欲しいという事なのか、も、、

まああとtypescriptしか提供されないので、みんなtypescriptで書いてねという、ある程度思想強めに出来ているとは感じました。まあでもこれはいつもの流れといえばそうなんですよね。では。

1

Discussion

ログインするとコメントできます