Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Page Laravel Package

nuewire/page

Nuewire Page is a Laravel package for creating and managing dynamic website pages. It provides a structured way to define page content, routes, templates, and SEO metadata, integrating smoothly with Blade and existing Laravel apps so content stays editable without heavy CMS overhead.

View on GitHub
Deep Wiki
Context7

Getting Started

The install path for a package like this is three commands, but the first hour should be spent reading rather than writing code:

composer require nuewire/page
php artisan vendor:publish --tag="page-config"
php artisan migrate

Then confirm what actually landed before you build on it:

php artisan route:list | grep -i page      # what public routes got registered
php artisan about                          # is the provider auto-discovered?
php artisan db:table pages                 # or: mysql> DESCRIBE pages

Where to look first, in order:

  1. config/page.php — route prefix, middleware stack, template map, cache settings. Nearly every "why doesn't this work" question resolves here.
  2. The migrations — they define your actual data contract (slug, status, published_at, template, and whatever content/meta columns ship). Read them before writing a single Page::create(); guessing at fillable fields is the most common day-one waste of time.
  3. The published views — php artisan vendor:publish --tag="page-views" and read the default template. It tells you exactly what variables the renderer passes and how content is expected to be shaped.

First use case — a static marketing page:

use Nuewire\Page\Models\Page;

Page::create([
    'slug'      => 'about',
    'title'     => 'About Us',
    'status'    => 'published',
    'template'  => 'default',
    'content'   => ['blocks' => [/* ... */]],
]);

Hit /about. If you get a 404, the route prefix or route registration is off; if you get a 200 with a blank body, it's almost always a template key that isn't in the config map (see Gotchas).

The realistic day-to-day use case is narrower than the description suggests: a handful of evergreen pages that a non-developer occasionally edits — About, Terms, Privacy, a landing page. It is not a CMS, and treating it as one is where teams get burned.

Implementation Patterns

Wrap the package, don't spread it. The single highest-value thing you can do on day one is create one thin adapter and route all app code through it:

// app/Support/PageRenderer.php
class PageRenderer
{
    public function render(Page $page): View
    {
        $view = config("page.templates.{$page->template}")
            ?? config('page.templates.default');

        return view($view, ['page' => $page]);
    }
}
{{-- resources/views/components/page-render.blade.php --}}
<div class="prose">
    {{ $slot }}
</div>
<x-page-render>
    @include(app(PageRenderer::class)->render($page)->name(), ['page' => $page])
</x-page-render>

When 1.1.0 changes the internal query or template resolution, the diff is one file instead of a dozen Blade views.

Templates as config, not code. Register every template in config/page.php so adding one is a config change:

'templates' => [
    'default'   => 'pages.default',
    'landing'   => 'pages.landing',
    'legal'     => 'pages.legal',
],

Then Page->template stores a key, never a view path. Storing pages.landing directly in the database couples your content to your filesystem and makes renames a data migration.

Route model binding over hand-rolled lookups. If the package registers {page} by slug, use it. Consistent 404 handling and any soft-delete/status scoping come free. If you need custom resolution, publish the routes (--tag="page-routes") and override the binding rather than writing a parallel controller:

Route::bind('page', fn ($slug) => Page::published()->where('slug', $slug)->firstOrFail());

Treat content JSON as the source of truth, derive everything else. If there's a JSON blocks column, don't add columns to the package table. Use an observer to project derived data into your own table:

Page::saved(function (Page $page) {
    SearchIndex::updateOrCreate(
        ['page_id' => $page->id],
        ['body' => strip_tags($page->renderedHtml())]
    );
    Cache::forget("page:{$page->slug}");
});

Seed your pages. For a marketing site, pages are fixtures, not user data. Ship an idempotent seeder with updateOrCreate(['slug' => ...]) so every environment — including CI and fresh local installs — has the same baseline. Tests that hit /about then don't depend on manually created records.

Feature-flag rollouts in your app, not the package. Wrap the public route in your own middleware or gate rather than leaning on the status column for staged launches. Content status and deployment status are different concerns and conflating them makes both harder to reason about.

Gotchas and Tips

  • The changelog is a link, not a changelog. 1.0.1 ships only a GitHub compare URL. Before bumping, run composer update nuewire/page -W --dry-run and read the actual diff. Do not assume "patch" means "safe" for a 0-star package with no test story you've verified.
  • Config cache will lie to you during upgrades. Published config from 1.0.0 can be missing keys added in 1.0.1, and config:cache freezes that gap in place. After every update: php artisan config:clear && php artisan route:clear. Re-publish config with --force into a temp directory and diff it against yours rather than overwriting.
  • Route collisions are silent until they aren't. An auto-registered public route will happily shadow a hand-written /pages/{slug}. Check route:list immediately after install and set a distinctive prefix in config if you have anything close.
  • Stale cache from raw SQL. If rendering is cached (keyed on updated_at or a hash), any write that bypasses save() — a DB::table()->update(), a bulk import, a direct DB edit — leaves stale output with no error. Always mutate through the model or fire the cache invalidation yourself.
  • Blank page, 200 OK. The classic failure is a resolved page with a template key absent from config/page.php. Debug with dd(config('page.templates'), $page->template) before touching anything else. Second most common: the view file exists but the package passes variables your template doesn't expect — dump get_defined_vars() in the published default view to see the real contract.
  • Migration ordering. Package migrations often timestamp ahead of yours. If a pages table already exists (from a previous package, or a homegrown one), guard the upgrade with a shim migration using Schema::hasTable() before running migrate.
  • Extension points, ranked by safety. Views (--tag="page-views") → model events (saved, deleted, deleting) → your own wrapper class. Extending the package's query builder or repository internals is the least safe option and the most likely thing to break in 1.0.2.
  • Pin conservatively. "nuewire/page": "^1.0" is right. Watch for 2.0.0 as the realistic breaking boundary for config keys and content schema — but with a package this young and this sparsely documented, budget for reading the diff at every minor too.
  • Decide early what it is not. The description mentions documentation sections and "lightweight CMS features." In practice, keep it to static, low-churn pages. The moment you need revisions, drafts with previews, or multi-author workflows, you will spend more time extending this package than you would have spent on a dedicated CMS. Draw that line before the content team discovers the admin route.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource