silber/client-templates
Laravel package for defining client-side templates with Blade, letting you reuse server-authored markup in browser-rendered interfaces. It helps keep JavaScript-driven UI templates consistent with your Laravel views without duplicating HTML.
Install it like any other package:
composer require silber/client-templates
This is a Laravel-5-era package, so check that the service provider still boots on your version. On Laravel 5.5+ Silber\ClientTemplates\ClientTemplatesServiceProvider may be auto-discovered from the package's composer.json; if it isn't, add it to config/app.php along with the ClientTemplates facade alias.
Next, publish the config:
php artisan vendor:publish --provider="Silber\ClientTemplates\ClientTemplatesServiceProvider"
The first place to look is config/client-templates.php. It tells you which directory the package scans for template files and what wrapper it emits them in (<script type="text/x-handlebars-template"> by default). Read the service provider's register()/boot() methods next — that's where the Blade directive or facade binding is defined if the config alone doesn't make the entry point obvious.
Your first real use case: move the inline <script type="text/x-handlebars-template">…</script> blocks (or Mustache/EJS equivalents) out of layouts/app.blade.php into individual files under resources/views/templates/, then render them all back into the layout with a single directive in the <head> or before </body>. You get separate, editor-friendly files instead of a 400-line layout, with the same output in the DOM.
One file per template, one directive in the layout. Put each client template in its own file (resources/views/templates/user-card.html, resources/views/templates/comment.html) and render the whole directory once:
{{-- layouts/app.blade.php --}}
@clientTemplates('templates')
Confirm the exact directive/facade name in the config and provider — the package is small enough that a 60-second read of src/ settles any ambiguity, and the name has changed across releases.
Scope templates per page. If a layout would otherwise ship every Handlebars template to every user, pass only the subdirectory or file list the current page needs:
@clientTemplates('templates/checkout')
Keep the directory layout aligned with features (templates/checkout/, templates/dashboard/) so this stays readable.
Let Blade do server-side interpolation, but deliberately. .blade.php templates under the configured path are compiled first, so you can inject asset(), csrf_token(), or a base URL into a client template. That's the main reason this package exists over dumping raw .hbs files — use it when the template needs a server-known value, and use plain .html files when it doesn't, so you're not paying for a compile step on static markup.
Pair with a client-side lookup. The package only writes the wrapper tags. On the JS side you still need something like:
const source = document.querySelector('#tmpl-user-card').innerHTML;
const render = Handlebars.compile(source);
Naming the wrapper <script id> from the file name (or supplying an explicit name) makes that lookup reliable.
Precompile in production if the engine supports it. Handlebars and similar engines ship CLIs that turn templates into JS functions at build time. For anything beyond a handful of templates, keep this package for local development and switch to precompiled output in your Vite/webpack build for production — you avoid shipping raw template strings and gain parse-time errors.
Version compatibility is the biggest risk. The package has a tiny install base and has not tracked modern Laravel. Verify the provider's method signatures still match the framework's service-provider contract, and be prepared to fork a two-file package if it breaks. Pin the version.
Blade will fight you inside .blade.php client templates. A Handlebars {{#each items}} or a literal @ in a template can be interpreted by Blade. Wrap the body in @verbatim … @endverbatim, or keep client templates as plain .html and reserve .blade.php for the ones that genuinely need server data.
@ directives are the silent killer. A stray @if or an email-like [email protected] in a template can produce a Blade parse error or swallow content. If a page renders blank, check storage/logs/laravel.log for a view-compile exception before suspecting the package.
Clear the view cache after editing templates. These go through the normal Blade compiler, so stale output after a change is almost always php artisan view:clear (or a php artisan optimize:clear).
Everything in a client template is public. The <script type="text/x-…"> block is inert but fully readable in page source. Never put API keys, admin URLs, internal IDs, or feature flags you don't want users to see inside one.
Debug by dumping the raw output. If nothing appears in the DOM, temporarily remove the directive's escaping or inspect the rendered HTML. The usual causes are (a) the config path pointing at a directory that doesn't exist relative to resources/views, and (b) the directive being placed in a section that the layout never yields.
Extension point: the wrapper tag. If you need text/x-vue-template, text/template, or a custom attribute, the cleanest hook is to override the config value for the script type, or wrap the package's renderer in your own Blade directive that post-processes the output. Forking is reasonable here — the package is small, and pinning a fork is often less work than fighting an abandoned release.
Don't use it for server-rendered partials. It renders client templates into the server view. If you find yourself reaching for it to include a Blade partial, you want @include — mixing the two produces confusing double-rendering bugs.
How can I help you explore Laravel packages today?