Public HTML recipe

The docs stack keeps a clean split between data and chrome. The plugins (filament-blog, filament-docs, filament-seo) own the Eloquent models, Filament resources, and the panel editors — they never ship themed Blade. The clone owns every public HTML surface: the landing page, the docs chrome and sidebar, the blog index, and the article view. Do not copy a plugin's themed pages; there are none to copy.

Routes

The clone's routes/web.php wires four public entry points. Verify against your clone; this is the shape used by the first instance.

Route::get('/', [PublicPageController::class, 'home']);
Route::get('/docs/{path}', [PublicPageController::class, 'docs'])->where('path', '.+');
Route::get('/blog/{slug}', [PublicPageController::class, 'post']);
Route::get('/sitemap.xml', [PublicPageController::class, 'sitemap']);

The first instance also registers /docs (index) and /blog (index) — add them if your product wants listing pages.

PublicPageController

One controller, four actions, no theming in the plugins.

  • home() renders the clone's landing Blade with SeoTags from SITE_HOME_TITLE / SITE_HOME_DESCRIPTION.
  • doc($path) loads a Doc by path, then abort_unless($doc->canBePreviewedBy(auth()->user()), 404). Staff see drafts; guests get 404. It passes the cached DocOutline::tree() to the sidebar partial.
  • post($slug) loads a Post by slug with the same preview gate.
  • sitemap() builds the URL list (/, published posts, published docs) and returns Sitemap::xml($urls).
flowchart LR
    A[Request] --> B[Route in web.php]
    B --> C[PublicPageController]
    C --> D[Published Doc / Post]
    D --> E[Clone Blade view]
    C --> F[DocOutline::tree cache]
    F --> G[site.partials.docs-nav]
    E --> G

Sidebar partial

The sidebar renders the cached outline recursively, unlimited depth. The first instance keeps a single partial that includes itself for children:

@include('site.partials.docs-nav', ['nodes' => $node['children']])

The docs index page indents three levels explicitly (root → child → grandchild) for the "All pages" list; deeper nesting still flows through the recursive partial.

Path overrides

Post::publicPath() defaults to /blog/{slug} and Doc::publicPath() defaults to /docs/{path}. Override them in the clone when the product uses other prefixes — Articles at /articles, Guides at /guides, etc. The plugin config (or Plugin::make()->publicPrefix('articles')) is the supported seam; defaults stay blog / docs for the first instance. See ticket 06.

See also

Built by Qcentic