Skip to content

Filament Atelier

Six pages into blocks, and the rule was nobody notices

28 August 2026 3 min read

Contact and five listing pages moved from Blade views and controllers to Atelier blocks. The site was already live and indexed, so the constraint wasn't building a page builder, it was making sure nobody could tell it had happened.

Filament v5 is in. The routing collision is fixed. Now the actual work: converting real pages on a live, indexed site to Atelier blocks, without anybody noticing.

Six pages first: Contact, and the five listing pages, projects, services, blog, devlogs, resources. Home stays on its own controller for now, it's the most structurally different page on the site and gets its own pass later. The constraint on all six: identical rendered output. Same URLs, same routes, no reindexing, no redirect rules. Where a section repeats across multiple pages, it's one block I can select from a list, not five copies of the same markup with different data wired in.

ListingBlock renders projects, services, blog posts, devlogs or resources from one block, dispatching to the right card component by a source attribute. The first version used Blade's <x-dynamic-component :attributes="[...]"> to pass the current item through.

Every card rendered. Every card linked to #.

<x-dynamic-component> doesn't spread an array through :attributes the way I expected. It only forwards attributes written literally on the tag itself. The array reached the component as one opaque prop, not as named attributes the component's @props could see, so $project inside the card was always null. Replaced the dynamic dispatch with an explicit @switch($source) calling each <x-ui.project-card>, <x-ui.blog-card> and so on directly. Less clever, correctly wired.

A block used for the wrong shape

The Resources page ends with a closing paragraph, full width. I'd wrapped it in TwoColBlock, since a two-column grid block already existed and one column just meant leaving the second empty. It rendered at half width instead, because TwoColBlock is a two-column grid whether or not both columns get used.

The fix wasn't a CSS override, it was admitting TwoColBlock was the wrong tool: a genuine two-column layout and a single block of prose are different shapes, not the same shape used differently. Built ProseBlock for the second case and moved the Resources page onto it.

Click-to-select broke twice, for two different reasons

Every block needs to be clickable in the editor's live preview, so clicking a section on the canvas opens its settings in the sidebar. It stopped working twice.

First: none of this site's <x-ui.*> components echo {{ $attributes }}, so the data-atelier-block id Atelier needs on a section's root element never reached the DOM. Fixed by wrapping every block's output in its own <div {{ $shared }}>, independent of whatever the inner component does with its own attributes.

Second, after the first fix: still nothing happened on click. The custom layout this site uses, adapted from the existing pages/layout.blade.php, never carried over the preview-mode script that listens for clicks on [data-atelier-block] and posts a message back to the editor iframe. Copied it in from the package's own default layout, gated behind the $preview flag so it never ships to a real visitor.

Where it stands

Six routes removed from web.php, six controllers' index() actions gone, six orphaned Blade views deleted. Every route() call pointing at one of those six names, in every template that used them, swept to url() instead. Sixteen blocks in the editor's picker. Every one of the six pages, screenshot-compared against what was live before, matches. The visitor sees nothing. That was the whole point.