TL;DR — A view shipped inside cleaniquecoders/laravel-db-doc opened with <x-guest-layout> and rendered <x-jet-authentication-card-logo>. Both of those belong to the host application. In an app without Jetstream, the page fatals at render — and worse, php artisan view:cache fails for the whole application, because the Blade component tag compiler has to resolve every component tag it compiles. The fix: make the package view self-contained HTML with zero host-app dependencies.
How this bug hides
The package renders a database schema as Markdown and hands it to a Blade view:
return view('laravel-db-doc::markdown', [
'content' => Str::markdown($schema),
]);
That view was written a long time ago, in an app that happened to have Jetstream installed. It looked like this:
<x-guest-layout>
<div class="pt-4 bg-gray-100">
<div class="min-h-screen flex flex-col items-center pt-6 sm:pt-0">
<div>
<x-jet-authentication-card-logo />
</div>
<div class="w-full sm:max-w-2xl mt-6 p-6 bg-white shadow-md overflow-hidden sm:rounded-lg prose">
{!! $content !!}
</div>
</div>
</div>
</x-guest-layout>
Read that as a package author and the problem is obvious in hindsight: the package is reaching up into the application's namespace. x-guest-layout is resources/views/components/guest-layout.blade.php — in the app, not in the package. x-jet-authentication-card-logo ships with Jetstream. Neither is a dependency the package declares in composer.json, and neither is something the package can promise exists.
Think of it like a library method calling a global function it never defined. It works on your machine because your machine happens to have it.
The part that actually hurt
A broken page is annoying but contained — you only pay for it when someone visits the route.
view:cache is not contained. Here's the thing: Blade's component tag compiler doesn't lazily resolve <x-foo> at render time only. When you compile a view containing a component tag, the compiler has to resolve that tag to a class or an anonymous component file at compile time to know what to generate. view:cache walks every view path registered with the view finder — including the ones your packages registered via loadViewsFrom() — and compiles all of them.
So one unresolvable component tag in one vendor view takes down the deploy step for the entire app:
php artisan view:cache
Unable to locate a class or view for component [jet-authentication-card-logo].
The failure is reported in the host app's deploy pipeline, pointing at a vendor file the person deploying has never opened. That's a rough afternoon for someone who just ran composer require.
The fix: own your whole document
There is no clever fix here. The package view has to be a complete document that assumes nothing:
<!DOCTYPE html>
{{--
Self-contained schema documentation page. Deliberately free of host-app
Blade components (layouts, Jetstream, etc.) so the package renders — and
the host app's `view:cache` compiles — in any application.
--}}
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>{{ config('app.name') }} · Database Schema</title>
<style>
body { margin: 0; background: #f4f4f5; color: #18181b; line-height: 1.6; }
.wrap { max-width: 56rem; margin: 0 auto; padding: 2.5rem 1rem 4rem; }
.card { background: #fff; border: 1px solid #e4e4e7; border-radius: .5rem; padding: 2rem; overflow-x: auto; }
/* … table, code, pre rules for the rendered Markdown … */
</style>
</head>
<body>
<div class="wrap">
<div class="card">
{!! $content !!}
</div>
</div>
</body>
</html>
Three deliberate choices in there:
Inline <style>, not Tailwind classes. The old view leaned on bg-gray-100, prose, sm:rounded-lg — utility classes that only mean something if the host app compiled Tailwind and included the typography plugin. A package view styled with utility classes silently renders unstyled in an app that doesn't. Inline CSS is unfashionable and completely dependency-free. For a single internal utility page, that trade is easy.
<meta name="robots" content="noindex, nofollow">. This page is a map of your database. It should never end up in a search index if someone leaves the route open on a staging box. Cheap insurance, one line.
Only config('app.name') and app()->getLocale() from the outside. Both are framework guarantees, not app conventions. That's the line: a package may depend on Laravel, never on your Laravel app.
The rule I'd write on the wall
A package view may depend on the framework. It may never depend on the application.
If your package genuinely needs to sit inside the host's chrome, don't hardcode it — make it configurable and default to standalone:
// config/laravel-db-doc.php
return [
// Optional host-app layout to extend. Null = fully standalone page.
'layout' => env('DB_DOC_LAYOUT'),
];
@if($layout = config('laravel-db-doc.layout'))
@extends($layout)
@section('content') {!! $content !!} @endsection
@else
{{-- standalone document --}}
@endif
Opt-in coupling is fine. Assumed coupling is the bug.
Catching it before your users do
The cheapest possible regression test is the one that would have caught this in CI on day one:
it('renders the schema page in an app with no host components', function () {
$this->get('/db-doc?format=markdown')
->assertOk()
->assertSee('<!DOCTYPE html>', escape: false);
});
it('does not break the host application view cache', function () {
$this->artisan('view:cache')->assertSuccessful();
});
That second test is the important one, and it's easy to forget because it doesn't test your feature — it tests that your package doesn't damage the thing it's installed into. In a package test suite running on Testbench, the skeleton app is deliberately bare: no Jetstream, no Breeze, no app-level components. Which is exactly the environment your view needs to survive.
Takeaway
Package code gets audited for PHP dependencies — composer.json makes those explicit and a CI matrix proves them. Package views get no such treatment. Every <x-...> tag in a vendor view is an undeclared dependency on the host app, and the compiler will collect on it at view:cache time, in someone else's deploy pipeline.
Audit the Blade in your packages the same way you audit the PHP. Then add view:cache to the test suite so the audit doesn't have to happen twice.
Top comments (0)