tightenco/jigsaw-docs-template
A ready-made documentation site template for Jigsaw, the Laravel Blade-powered static site generator. It provides layouts, styling, navigation, and content structure so you can launch clean, fast docs without building the scaffolding from scratch.
This is a scaffold, not a dependency. You never composer require it — you instantiate it once and own the result forever. Internalize that before the first command, because every downstream decision (upgrades, CI, fork ownership) follows from it.
composer create-project tightenco/jigsaw-docs-template my-docs
cd my-docs
npm install
npm run dev # template-provided: Vite dev server + asset pipeline
./vendor/bin/jigsaw serve # or the composer script; serves the compiled site
The build needs PHP 8.x, Composer, Node (Vite 6-era, so Node 20+), and Tailwind. Pin all four now — a latest install is how this template first bites you.
Where to look first, in this order:
config.php — the whole information architecture lives here: baseUrl, pretty, collections (each collection's source path, sort key, permalink), markdown options, and build destinations (build_local / build_production).source/_layouts/ — the master and docs layouts. This is where @yield('content'), @stack/@push and the sidebar slot are defined.source/_partials/ — nav, head, footer, admonitions. Your customization effort concentrates here, not in Markdown.source/docs/ (or whatever your collection path is) — one Markdown file per page, each with a front-matter contract.package.json + vite.config.js — confirms the asset story and whether the dev script watches content or only assets.First use case — do this before you plan anything else: add a single page end to end.
---
title: Installing the SDK
order: 2
section: getting-started
extends: _layouts.docs
section: content
---
Save as source/docs/installing-the-sdk.md, confirm it appears in the sidebar, confirm $page->getUrl() resolves, then push build_production/ to a preview host. If that round-trip takes under 30 minutes you have a real build-time number and a real answer on hosting fit. If it takes a day, you have learned the template's shape cheaply instead of after a 40-page port.
Let config.php drive navigation, not your Blade. Define collections once and read them everywhere:
'collections' => [
'docs' => [
'path' => 'docs',
'sort' => 'order',
'extends' => '_layouts.docs',
],
],
Then in source/_partials/nav.blade.php, build the sidebar from the collection variable Jigsaw injects for you:
@foreach ($docs->sortBy('order') as $item)
<a href="{{ $item->getUrl() }}"
class="{{ $page->getUrl() === $item->getUrl() ? 'font-semibold text-blue-600' : '' }}">
{{ $item->title }}
</a>
@endforeach
Grouping by section front matter is the cheapest way to get nested IA without hand-maintaining a nav file. The moment someone has to edit two places to add a page, the docs start rotting.
Compose with partials, not custom directives. Jigsaw compiles Markdown through Blade before rendering, so @include works inside .md files. That gives you reusable callouts without touching PHP:
{{-- source/_partials/callout.blade.php --}}
<div class="border-l-4 border-{{ $type === 'warning' ? 'amber-500' : 'blue-500' }} p-4">
{{ $slot ?? '' }}
</div>
@include('_partials.callout', ['type' => 'warning'])
Breaking change in v2.
@endinclude
Push build-time work into bootstrap.php. This is the legitimate extension point: beforeBuild / afterBuild hooks are where a sitemap generator, a Pagefind search index, or a link checker belongs. Search is the single most common gap — bolt it on post-build rather than forking the theme.
Keep the day-to-day loop boring. Two terminals: Vite dev server for asset HMR, jigsaw serve for content. If the template's dev script doesn't watch Markdown, say so in the README rather than letting each writer discover it. Writers should be editing .md and refreshing, full stop.
CI is where this pattern pays off. Composer cache, npm cache, npm run build, jigsaw build production, publish build_production/. Wire a preview deploy per PR on day one — it converts "did my Markdown render?" into a link in the pull request and removes the fastest route to a broken docs site (merge-and-pray).
Handle redirects as a first-class artifact. Static output has no redirect server. If you change a permalink, you need a host-level rule (Netlify _redirects, Cloudflare rules, S3 routing rules) or a meta-refresh page. Generate both from a single source if URL churn is expected.
{{ $variable }} inside a fenced block will be executed, not printed. Wrap examples in @verbatim … @endverbatim or prefix as @{{ }}. This is the number-one source of broken docs pages, and it only surfaces on the page that matters most.baseUrl must match deployment exactly. A GitHub Pages project site needs /repo-name; get it wrong and every sidebar link 404s while the homepage looks perfect. Verify on the preview URL, not localhost.pretty: true requires host support. Clean URLs are free on Netlify/Cloudflare; on S3 you need index-document configuration or the trailing-slash variants break. Confirm before you commit to a permalink scheme.build_local at least once. Put the destination in the deploy script and never type build_production/ by hand.sort in the collection config or order in front matter. Set both, or set neither and accept it deliberately.vendor/, node_modules/, or build_*. Build in CI from a lockfile. Treat the whole build chain as a locked artifact, containerized, pinned.extends/section keys match the layout's @yield → collection path matches the file's directory → permalink collision. Ninety percent of "it renders blank" is a section name that doesn't match the layout.404.blade.php early. Static hosts fall back to it differently, and you'll want to point lost readers at the sidebar rather than a host default.How can I help you explore Laravel packages today?