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 withSeoTagsfromSITE_HOME_TITLE/SITE_HOME_DESCRIPTION.doc($path)loads aDocbypath, thenabort_unless($doc->canBePreviewedBy(auth()->user()), 404). Staff see drafts; guests get 404. It passes the cachedDocOutline::tree()to the sidebar partial.post($slug)loads aPostbyslugwith the same preview gate.sitemap()builds the URL list (/, published posts, published docs) and returnsSitemap::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.