PWA-ready Nuxt 4 monorepo: local-first OPFS storage, offline support, and built-in SEO/AI discoverability.
Live at https://zschzen.github.io/nuxt-template/
- Nuxt 4 (Vue 3, Vite 8)
- UnoCSS + shadcn-vue
- Turborepo + pnpm workspaces
- PWA — offline support, push notifications, OPFS storage
- TinyBase — reactive local-first store, persisted to OPFS via OpfsPersister
- NuxtSEO — robots, sitemap, schema.org, OG images,
llms.txt - Zod — env validation at startup
├── apps/
│ └── web/ # Main Nuxt application (PWA-enabled)
│ ├── app/ # Vue app source (pages/, components/, layouts/)
│ └── design/ # pen.dev design files (.pen)
├── packages/
│ ├── env/ # Zod env validation (fail-fast typed config)
│ ├── seo/ # Shared SEO layer (@nuxtjs/seo + AI-crawler support)
│ ├── storage/ # OPFS storage composables (gzip, ZIP import/export, TinyBase store)
│ ├── ui/ # UI components (shadcn-vue + UnoCSS)
│ └── eslint-config/ # Shared ESLint config
├── pnpm-workspace.yaml # Named catalog-managed dependency versions
└── turbo.json # Turborepo pipeline config
cp apps/web/.env.example apps/web/.env
pnpm install
pnpm dev| Command | Description |
|---|---|
pnpm dev |
Start development server |
pnpm build |
Build for production |
pnpm lint |
Lint all packages |
pnpm typecheck |
Type-check all packages |
SEO ships as a shared Nuxt layer (packages/seo) enabled in any app with one line:
export default defineNuxtConfig({
extends: ['@template/seo'],
})The layer bundles @nuxtjs/seo (robots, sitemap, OG images, schema.org, canonical URLs, link checking) plus two standalone modules:
| Module | Purpose |
|---|---|
nuxt-ai-ready |
Generates /llms.txt and /llms-full.txt for AI crawlers |
nuxt-skew-protection |
Prevents ChunkLoadError / stale-chunk errors during deployments |
| Endpoint | Description |
|---|---|
/sitemap.xml |
XML sitemap for search engines |
/llms.txt |
Markdown index of the site for LLMs |
/llms-full.txt |
Full page content in markdown for AI crawlers |
Each app declares its own site identity — required by the sitemap module:
// apps/web/nuxt.config.ts
export default defineNuxtConfig({
site: {
name: 'Template App',
description: 'PWA-ready Nuxt template with offline support',
url: 'https://zschzen.github.io/',
},
})GitHub Pages note: this app serves from a subpath (/nuxt-template/), so robots.txt generation is disabled (robots.robotsTxt: false). Full layer docs: packages/seo/README.md.
Dependencies are categorized using pnpm named catalogs in pnpm-workspace.yaml. Each catalog name communicates purpose, not just version:
| Catalog | Purpose | Example |
|---|---|---|
core |
Framework runtime | nuxt, vue, pinia |
vueuse |
Composable utilities | @vueuse/core, @vueuse/nuxt |
pwa |
PWA & push tooling | @vite-pwa/nuxt, web-push |
styling |
CSS engine + themes | unocss, preset-shadcn |
ui |
Component library | reka-ui, shadcn-nuxt, vaul-vue |
linting |
Code quality | eslint, @antfu/eslint-config |
types |
TypeScript + type defs | typescript, vue-tsc, @types/web-push |
validation |
Schema validation | zod |
devtools |
Dev tooling | turbo, taze, dotenv-cli |
git-hooks |
Pre-commit checks | husky, lint-staged |
icon |
Icon fonts | @iconify-json/lucide |
modules |
Nuxt modules | @nuxt/image, @nuxtjs/color-mode |
- Categorization —
catalog:lintinginpackage.jsontells you what a dep is for, not just its version - Single version source — update once in
pnpm-workspace.yaml, all packages pick it up - Review-friendly — dep upgrades show which catalog changed, making PRs easier to review
# Add to a named catalog in pnpm-workspace.yaml first, then:
pnpm --filter <package> add <dep> catalog:<catalog-name>pnpm --filter @template/ui dlx shadcn-vue add buttonApp screens are mocked as pen.dev .pen files in apps/web/design/ — JSON-based, versioned alongside code. Workflow and conventions: apps/web/README.md.
MIT