novius/laravel-nova-visual-composer
A visual page composer for Laravel Nova that lets you build and arrange content blocks with a drag-and-drop interface, so editors can assemble layouts without touching code.
Architecture fit. This package is a Nova field, not a rendering engine or CMS. It occupies a narrow seam in the architecture: a single JSON column on an Eloquent model, a config-declared block registry (key, label, editor component, nested Nova fields), and a Vue field component that provides drag-and-drop composition inside the Nova admin. Everything downstream of the column — routing, Blade/API rendering, caching, media resolution, localization — is application code you own. That is a good architectural fit for teams that already treat Nova as their internal admin surface and want structured content instead of a single rich-text blob or hand-authored HTML. It is a poor fit for anything that expects the package to be a content service, an API layer, or a customer-facing builder. The critical architectural property is that content is stored as portable JSON with a type discriminator, so the persistence model survives the package's abandonment even if the editor does not. That decoupling is what makes this evaluable at all at 14 stars.
Integration feasibility. Mechanically low-effort: composer require, publish config/assets, add a json('content')->nullable() column, cast to array, register VisualComposer::make('Content', 'content') on a resource, define two block types. Realistically a one-day spike to prove round-trip. The gating question is not effort but resolution: last release July 2022, sitting in the Nova 3/Vue 2 → Nova 4/Vue 3 transition window. Two facts must be confirmed before any commitment: whether composer require --dry-run resolves against your pinned laravel/nova constraint, and whether the shipped Vue component targets Vue 2 or Vue 3. Those answers determine whether the field renders at all — and because a non-hydrating field can save an empty payload over existing content, this check must happen on a throwaway branch against non-production data.
Technical risk. The dominant risk is abandonment, not defects. Zero dependents means no one else is filing the bugs you will hit, and Nova support explicitly will not touch third-party fields. Secondary risks, in order of consequence:
dist/ ships, you inherit an npm run prod step inside a PHP deploy pipeline, a permanent maintenance cost.Key questions.
schema_version from day one, save-time validation, skip-unknown-on-render?Stack fit. Acceptable only where the stack matches the era it was built for: Laravel 8/9, PHP 8.0/8.1, Nova 3/4, Vue 2 or early Vue 3. Against Laravel 11/12 + Nova 5 there is no compatibility guarantee and no maintainer to provide one. The practical test is the resolver plus a visual check of the field in the Nova resource edit screen on a staging environment.
Migration path. Recommend a fork-first posture rather than a dependency posture. Copy the field class, the Vue component, and the config into an application namespace, pin it, and treat upstream as a reference implementation. Roughly one day of work that removes most of the abandonment risk and gives you a legal place to patch Nova 5 compatibility later. Adoption sequence:
rich_text, image), save a page, dd($page->content), render via Blade. Freeze the observed payload shape as your schema and add schema_version to every stored block immediately.fields array + block-count cap ~100), decode guard treating malformed JSON as empty with a log, View::exists() guard on render, content:repair-blocks artisan command.saved observer that warns when a previously non-empty column is written empty.Compatibility. Two hard constraints. First, renderer/editor version coupling — a Vue 2 field will not hydrate in a Vue 3 Nova, and the failure is silent. Second, payload compatibility is entirely on you — the package has no migration tooling for block shapes, so any change to the registry requires a backfill command that rewrites stored JSON. Never rename a block key in place. Treat templates as JSON fixtures in seeders/config rather than database-only, so staging and production stay in sync.
Sequencing. Do not parallelize editor work and rendering work. Prove the round trip end to end with two block types before adding a third. Build the renderer before the block library grows, because the renderer's skip-unknown behavior is what makes schema evolution survivable. Defer nesting (columns, accordions, sections) until the flat contract is frozen and tested — nesting makes the payload recursive and roughly doubles the blast radius of a schema mistake. If localization is even a plausible requirement, add the per-locale column before authoring begins.
Maintenance. Budget ~2–3 days per year plus a defined task in every Laravel and Nova major upgrade. The recurring work is not the package code — it is the schema: backfills on every block rename, repair runs on malformed JSON, and renderer updates as the block library evolves. Internalizing the code converts unpredictable "upstream is dead" risk into predictable "we own a small field" work, which is the trade worth making. Deploy runbook must carry vendor:publish next to migrate, plus a post-deploy smoke test that loads the resource edit page — a blank field in admin is almost always an unpublished or unbuilt asset, not a bug, and that distinction saves hours of misdirected debugging.
Support. There is effectively no upstream support channel: single small-agency maintainer, no release since 2022, no dependents, and Nova support will not diagnose third-party fields. Internal onboarding therefore depends on documentation you write: the block contract, the payload schema, the renderer's skip-unknown semantics, the publish step, and the fork/patch procedure. Handing a second developer the vendor directory without that doc is a bus-factor problem, not a knowledge-transfer problem.
Scaling. The single-JSON-column design scales fine for pages and bounded documents. It degrades on documents with hundreds of nested blocks: save payload size and per-request render time both creep. Mitigations are cheap — cache rendered fragments around the render loop or via a cached model accessor for high-traffic pages, cap block count per document, and keep block partials free of N+1 media lookups by resolving images through the existing conversion/thumbnail pipeline rather than the raw File value. If the requirement grows toward a customer-facing builder or API-first content delivery, this column shape is the wrong substrate and the migration cost is real.
Failure modes.
| Failure | Symptom | Mitigation |
|---|---|---|
| Vue component fails to hydrate | Empty payload written over existing content, no error | Revisions/backup column, staging check on every Nova upgrade, saved observer warning on non-empty→empty |
| Unpublished/unbuilt assets | Blank field in admin | Publish step in deploy runbook + post-deploy smoke test |
| Unknown block type in stored JSON | Cosmetic gap or 500 on live page | View::exists() guard; skip on render, reject on save |
| Malformed JSON | Unhandled decode exception | try/catch → empty block list + log; content:repair-blocks command |
| Removed/renamed block key | Data orphaned or renders wrong component | schema_version per document, backfill command, never rename in place |
| Document bloat | Slow saves and slow renders | Block-count rule, fragment caching |
The asymmetry worth institutionalizing: fail loudly for the editor, degrade gracefully for the visitor. Reject unknown types on save; skip them on render.
Ramp-up. Low. The config schema reads in twenty minutes and the extension points are few — the field class, the Vue component, and the block registry. A competent Laravel developer is productive in the spike on day one and comfortable owning the fork within a week. The genuine ramp-up cost is organizational, not technical: naming an owner, committing to the quarterly maintenance budget, and getting product to accept that the block registry is the content model — which means block changes are schema migrations, not configuration tweaks.
Verdict. Adopt as a reference implementation and spike candidate, not a platform dependency. Green-light a one-week, two-block, end-to-end validation on a throwaway branch to confirm the block-based content model is the right product decision. Treat a production commitment as contingent on the fork being internalized, a named owner assigned, schema_version in the payload from day one, and the renderer's safety guards written before the editor ships. Look elsewhere if the requirement is really previews, versioning, scheduling, localization, accessibility guarantees, or multi-tenant authoring — those are CMS requirements this package does not address.
How can I help you explore Laravel packages today?