norman-huth/nova-assets-changer
Laravel Nova package that lets you change the CSS and JavaScript assets Nova serves, so you can customize your admin panel's appearance and behavior without editing vendor files directly.
Prerequisites. A Laravel app with a licensed Laravel Nova install. This package sits on top of Nova's published asset bundle (vendor/laravel/nova/public), so it is useless without Nova and cannot be evaluated in a repo that doesn't have Nova credentials in CI. Nova 5 is supported as of v1.2.4; Nova 4 behavior after that fix is unverified.
Identity check first. The registry entry is norman-huth/nova-assets-changer but the repository and composer namespace are Muetze42/.... Before you composer require anything, confirm the package you install is the code you audited. If the names don't resolve to the same source, stop and investigate — this is a supply-chain question, not a typo.
Minimal steps.
# Pin exactly. Never a caret range on an archived package.
composer require muetze42/nova-assets-changer:1.2.4
# Publish the config (confirm the exact tag in the package's service provider —
# the README is thin, so read src/ before assuming).
php artisan vendor:publish --provider="Muetze42\NovaAssetsChanger\NovaAssetsChangerServiceProvider"
# Clear config cache after any change
php artisan config:clear
Where to look first, in order:
vendor/muetze42/nova-assets-changer/src/ — the service provider and whatever command it registers. It is a small package; read all of it in twenty minutes. You need to know exactly which config keys it reads and whether it mutates files on disk or only rewrites URLs at render time.if (Nova::version() >= 5)) or a hard-coded asset name. That single detail tells you how badly the next Nova major will break you.composer.json of v1.2.4 — check the laravel/nova constraint. If it no longer admits ^4.x, you are on a Nova-5-only package.First use case: serve Nova's assets from a CDN instead of the app origin. This is the lowest-commitment way to exercise the package and the most common reason to want it. Point Nova's CSS/JS at cdn.example.com in production, leave local dev on the default local paths, and confirm the admin login page renders with the CDN URLs in the markup. That exercises config resolution, URL rewriting, and cache-busting in one pass without touching a single line of Nova's shipped bundle.
The core pattern is one config value driven by env, so each environment resolves Nova's asset root differently:
// config/nova-assets-changer.php — illustrative shape; match the real keys
return [
'asset_url' => env('NOVA_ASSET_URL'), // null = Nova's default
'version' => env('NOVA_ASSET_VERSION'),
];
# .env.production
NOVA_ASSET_URL=https://cdn.example.com/nova
NOVA_ASSET_VERSION=${BUILD_ID}
Local and staging leave NOVA_ASSET_URL empty and fall through to Nova's own paths. That keeps the dev loop fast and means a broken CDN config can never take down your local admin.
nova:publish, never by handThis is the single most important integration decision. nova:publish writes Nova's stock assets into public/vendor/nova; anything you do to those paths must happen afterwards or it gets clobbered.
// composer.json
"scripts": {
"post-autoload-dump": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"deploy": [
"@php artisan nova:publish --ansi",
"@php artisan nova-assets:change --ansi" // confirm the real command name
]
}
Whatever the actual command is, wire it into the deploy script as a numbered step. If the step depends on a human remembering to run it, it will be skipped on the one deploy where it matters.
Static assets behind a CDN go stale. The version/query-string must change on every deploy that ships new CSS/JS — derive it from your build ID or commit SHA, not from a hand-edited number in a config file. If you're serving per-tenant or per-brand asset sets (see below), each set needs its own version token, or one tenant's cached bundle will leak into another's admin panel.
Environment-aware config makes it feasible to serve different Nova asset bundles per brand from one codebase. The pattern that holds up:
// AppServiceProvider::boot() or a small resolver
config(['nova-assets-changer.asset_url' => tenant()->nova_asset_url]);
Only do this if the config is read late enough in the lifecycle that a per-request override takes effect. If the package resolves the URL in a service provider's register() or during a boot-time file mutation, per-request overrides won't work and you need one deployment per brand instead. Verify this with a two-tenant test before committing to the design — it's the difference between "cheap white-label" and "we just built a fork."
Copy the package's source into your own repo — app/Support/NovaAssets/ or a packages/ path repository — rewrite the namespace, and treat it as first-party code. You get:
composer update can't move you across a Nova major.The cost is roughly a few hundred lines of code you now maintain. Given zero dependents and a single maintainer, that is a fair trade.
Because Nova is a paid credentialed dependency, this needs a licensed Nova install in the pipeline — there is no way around it. Assert two things:
<body>.cdn.example.com/nova or the build version token).A blank admin panel after an upgrade is the failure mode you're guarding against, and it's silent — the page returns 200 with an unstyled or empty shell.
The repo is archived. You own this now. There is no upstream fix line. For any new adoption, either vendor it or don't adopt it. The v1.2.4 "Nova 5 fix" from a first-time contributor is itself the evidence: Nova's asset layout changes across majors and nobody upstream is going to patch the next one.
Never use a caret constraint. The changelog skips v1.2.2 → v1.2.4 with no v1.2.3 release note. A ^1.2 range would move you across a Nova major silently. Pin 1.2.4 exactly, and once vendored, freeze it in your own tree.
Unaccounted-for release. v1.2.3 has no notes. Before treating v1.2.4 as a single-purpose Nova 5 patch, diff the two tags or read the commit log. Small packages hide surprises fine.
Coupling to Nova internals is the real risk, not the code quality. The package hooks Nova's compiled asset filenames and hashes, which is not a stable public contract. Every Nova upgrade needs an explicit re-verification step — budget roughly half a day per Nova major, and put it on the upgrade checklist as a dependency to validate, sequenced after Nova, never before. The package follows Nova; it cannot be proven ahead of it.
nova:publish overwrites. Order matters in the deploy script and in any local setup docs. If someone runs nova:publish after the changer step, the customization disappears with no error message.
Immutable containers / gitignored public/vendor. If your image build doesn't run the artisan step, the package does nothing at runtime. Either add it to the Dockerfile after nova:publish, or accept that this package isn't compatible with your deployment model. Don't discover this in staging.
Idempotency is unverified. Run the command twice in a row and diff the output. Asset-rewriting tools are frequently not idempotent — double-prefixing URLs (https://cdn.example.com/https://cdn.example.com/...) is the classic failure. Also test the sequence change → nova:publish → change to confirm recovery.
Nova 4 regression risk. Determine whether the Nova 5 fix preserved Nova 4 behavior or effectively made the package Nova-5-only. Check the laravel/nova constraint in v1.2.4's composer.json. If you're mid-migration from Nova 4, don't upgrade this package as part of that work — sequence Nova first.
Licensing / support. Modifying Nova's published assets may conflict with Nova's license and will likely void vendor support for asset-related issues. Nova Support will not help you debug a modified bundle. Confirm the terms internally before rollout, and be prepared to reproduce any admin-panel bug against a stock Nova install before filing a ticket.
CDN cache invalidation is on you. The package changes URLs; it does not purge caches. If you deploy new assets under the same path with the same version token, users get stale JS and the admin panel misbehaves for a subset of them — typically the ones on the flakiest connections, which makes it look like an intermittent bug rather than a cache bug.
Debugging recipe. When the admin panel looks wrong: view-source the login page and check which asset URLs are emitted; if they're stock, the config isn't resolving (check config:clear, then whether config caching froze an old value); if they're your URLs but the panel is broken, the files aren't where the URL says they are (check the deploy ordering); if it works locally and not in prod, it's almost always nova:publish running after your step or a CDN miss.
Scope discipline. This is a presentational patch layer. The moment the requirement touches auth, routing, field logic, or data, stop — that's a first-party Nova tool or a card, not an asset changer. Stretching this package is how a cosmetic patch becomes technical debt with a maintenance burden you didn't budget for.
Before you ship, answer these: Does v1.2.4's composer.json still admit ^4.x? Is PR #41 version-guarded or hard-coded? Does it work where public/vendor is gitignored? What's in v1.2.3? Is there a successor fork if the maintainer becomes unreachable? If you can't answer the first three, you don't have enough information to adopt.
How can I help you explore Laravel packages today?