Base Nuxt layer for cooperco projects
npm install @cooperco/nuxt-layer-baseA foundational layer that provides essential configuration and tooling for Nuxt 4 projects.
Compatibility Date: 2025-07-15
—
Nuxt Layers are a way to share configuration, modules, and app/server files across multiple Nuxt projects. An app “extends” a layer, and Nuxt will merge the layer’s modules, runtime configuration, and directory contents (like app/, server/, and plugins/) with the app’s own files. This lets teams centralize common tooling and conventions while each app remains free to add its own code.
Learn more in the official Nuxt Layers documentation: https://nuxt.com/docs/guide/going-further/layers
- TypeScript with strict mode enabled
- ESLint via @nuxt/eslint with stylistic Vue template rules
- Internationalization (i18n) via @nuxtjs/i18n
- Hints and best practices via @nuxt/hints
- Accessibility checks via @nuxt/a11y
- Nuxt DevTools enabled in development
- Optional error and event logging to Loggly (client and server), disabled by default
1) Add the layer to your project as a dev dependency
``bashnpm
npm i -D @cooperco/nuxt-layer-base
2) Extend the layer in your app’s Nuxt config
`ts
// nuxt.config.ts (in your app)
export default defineNuxtConfig({
extends: ['@cooperco/nuxt-layer-base']
})
`TypeScript strict mode and DevTools are active immediately. For ESLint, add a config in your app (see "ESLint in consumer apps"). For
@nuxt/hints, you must also install it as a dev dependency in your app (see "Hints and Best Practices (@nuxt/hints)").$3
This layer integrates the Nuxt i18n module. Use it directly in your components; preferred usage is an SFC
block. You can also call useI18n() in scripts.Basic usage in a component:
`vue
{{ t('hello') }}
Current locale: {{ locale }}
`Preferred: single‑file component local messages using an block
`vue
{{ t('welcome') }}
{{ t('cta') }}
{
en: {
welcome: 'Welcome',
cta: 'Click to continue'
},
fr: {
welcome: 'Bienvenue',
cta: 'Cliquez pour continuer'
}
}
`Both global files (e.g.,
locales/en.json) and per‑component blocks are supported.$3
This layer includes
@nuxt/hints, which provides real-time suggestions for improving your application's performance, security, and best practices directly in your development environment.#### Important: Runtime Dependency
Because
@nuxt/hints injects runtime code into your components (to track hydration and other metrics), you must add @nuxt/hints to your app's devDependencies even though it is provided by the layer. This ensures that the bundler can resolve the injected imports.`bash
npm
npm i -D @nuxt/hintspnpm
pnpm add -D @nuxt/hintsyarn
yarn add -D @nuxt/hints
`Note: If you are using a monorepo with workspaces (e.g., pnpm workspaces) where dependencies are hoisted to the root, this manual installation may not be necessary.
$3
This layer includes
@nuxt/a11y, which integrates accessibility checks and hints directly into your development workflow. It helps you identify and fix accessibility issues as you build your application.No additional configuration is required to use the basic features of
@nuxt/a11y once the layer is extended.$3
When enabled via environment variables, the base layer will:
- Capture server-side errors (Nitro request lifecycle)
- Capture client-side errors (Nuxt app errors, Vue component errors, global
window errors, unhandled rejections)
- Provide a useLogger() composable for custom logs (and a deprecated useLoggly() wrapper)
- Proxy all logs through the canonical route /api/log, where payloads are normalized and sensitive fields are scrubbed before forwarding to LogglyEnable in your app with environment variables:
-
NUXT_PUBLIC_LOGGLY_ENABLED=true
- NUXT_LOGGLY_TOKEN= (server-only)
- NUXT_PUBLIC_LOGGLY_TAGS=app-name,env (optional, comma-separated)
- NUXT_PUBLIC_LOG_LEVEL=error (optional; exposed to client for your own use)
- NUXT_LOGGLY_ENDPOINT=https://logs-01.loggly.com/inputs (optional override, server-only)Notes
- The token is never exposed to the client. Browsers only post to your app’s
/api/log endpoint (with /api/loggly kept as a deprecated alias).
- If logging is disabled or a token is not provided, the logging code is effectively a no-op and the API returns { ok: false, skipped: true }.
- LOG_LEVEL is exposed via runtime config but the base layer does not filter logs by level; you may use it in your own app logic.
- Back‑compat alias: /api/loggly remains available but is deprecated. Prefer /api/log.Tags behavior
- Automatic, source‑specific tags are appended for captured errors:
- Nuxt app‑level errors:
client, nuxt
- Vue component errors: client, vue
- Global window error events: client, window
- Unhandled rejections: client, promise
- Server‑side errors: server
- Your LOGGLY_TAGS (e.g., app-name,prod) and per‑call tags from useLogger() are merged with the automatic tags; duplicates are removed and the list is capped at 10 tags.Composable usage:
`ts
// inside a component or any composable
const log = useLogger()
await log.error('Checkout failed', { orderId, step: 'place-order' }, ['checkout'])
await log.warn('Slow response', { endpoint: '/api/orders', ms: 850 })
await log.info('User clicked CTA', { campaign: 'summer' }, ['marketing'])
await log.debug('State snapshot', { state })
`Automatically captured errors (when enabled):
- Server: request-time exceptions via a Nitro plugin
- Client: Nuxt app errors, Vue component errors,
window error events, unhandled promise rejectionsSecurity and privacy:
- The server proxy masks common sensitive keys in
meta (e.g., password, token, authorization, ssn, etc.)
- Logging is fire-and-forget; failures to log are swallowed to avoid breaking UXVerify locally:
1) Add env vars in your app (e.g.,
.env.local):
`
NUXT_PUBLIC_LOGGLY_ENABLED=true
NUXT_LOGGLY_TOKEN=...your token...
NUXT_PUBLIC_LOGGLY_TAGS=app-name,local
`
2) Start your app and trigger a test error.
3) Check the network tab for POST /api/log and then confirm the entry in Loggly.Troubleshooting:
- No logs? Ensure
LOGGLY_ENABLED=true and LOGGLY_TOKEN is present in the server environment.
- Browser CORS or network errors? The browser never talks to Loggly directly; it only posts to /api/log.
- High volume? Consider adding sampling or batching at the proxy later if needed.$3
This layer integrates ESLint via the
@nuxt/eslint module. When you extend this layer, ESLint is available to your app; you only need to add a config file and scripts in your project.1) Create
eslint.config.mjs in your app root (copy from this layer and adjust only if necessary)`js
// eslint.config.mjs (in your app)
// Nuxt generates a flat config at ./.nuxt/eslint.config.mjs
// We enable stylistic rules and add a few custom rules matching the base layer.
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'export default withNuxt({
rules: {
/ Stylistic /
'@stylistic/comma-dangle': [
'error',
'only-multiline'
],
'@stylistic/no-tabs': [
'error',
{ allowIndentationTabs: true }
],
/ TS /
'@typescript-eslint/no-unused-vars': [
'error',
{ caughtErrorsIgnorePattern: '^_' }
],
/ Vue /
'vue/html-closing-bracket-newline': [
'error',
{ multiline: 'never', selfClosingTag: { multiline: 'never' } }
],
'vue/html-closing-bracket-spacing': [
'error',
{ selfClosingTag: 'never' }
],
'vue/html-indent': [
'error', 'tab',
{ baseIndent: 0 }
],
'vue/multi-word-component-names': ['error', {
ignores: []
}],
'vue/component-name-in-template-casing': [
'error',
'kebab-case',
{ registeredComponentsOnly: false, ignores: [] }
],
'vue/component-options-name-casing': ['error', 'kebab-case'],
'vue/component-definition-name-casing': ['error', 'kebab-case']
}
})
`2) Add scripts and run ESLint
`json
{
"scripts": {
"lint": "nuxt prepare && eslint .",
"lint:fix": "nuxt prepare && eslint . --fix"
}
}
`Notes
- The base layer enables stylistic mode via Nuxt (
eslint.config.stylistic: true).
- Do not use Prettier alongside stylistic rules; remove Prettier configs/plugins from your app to avoid conflicts.
- Tabs are allowed for indentation (including in Vue templates). The no-tabs rule is enabled but permits indentation tabs.What this ESLint config enforces (in plain English)
- Base: It starts from Nuxt’s generated, project‑aware flat config (via
@nuxt/eslint), then turns on stylistic mode for consistent formatting in JS/TS/Vue files.
- This layer adds a few focused rules to keep things consistent and practical:
- @stylistic/comma-dangle: 'only-multiline' — Allow trailing commas only when the list spans multiple lines; disallow them on single‑line lists.
- Good (multiline, trailing comma ok):
`js
const user = {
id: 1,
name: 'Ada',
}
`
- Good (single‑line, no trailing comma): const arr = [1, 2, 3]
- @stylistic/no-tabs with { allowIndentationTabs: true } — Tabs are allowed for indentation but tabs elsewhere are flagged.
- @typescript-eslint/no-unused-vars with { caughtErrorsIgnorePattern: '^_' } — Flag unused variables, but allow a try/catch parameter that starts with _ when you don’t use it.
- Good (explicitly ignored):
`ts
try {
risky()
}
catch (_err) {
// intentionally ignored
}
`
- vue/html-closing-bracket-newline: { multiline: 'never', selfClosingTag: { multiline: 'never' } } — Don’t put a newline before a closing bracket, even for multi‑line attribute lists.
- Bad:
`vue
a="1"
b="2"
/>
`
- Good:
`vue
a="1"
b="2" />
`
- vue/html-closing-bracket-spacing: { selfClosingTag: 'never' } — No space before a self‑closing />.
- Bad:
- Good:
- vue/html-indent: ['tab', { baseIndent: 0 }] — Use tabs for indentation in Vue templates.
- Example:
`vue
Text
`
- vue/multi-word-component-names — Enforce multi‑word component names (no single generic names like Header conflicts). Configure ignores if needed.
- vue/component-name-in-template-casing: 'kebab-case' — In templates, component tags must be kebab‑case (e.g., , not ).
- vue/component-options-name-casing: 'kebab-case' — In SFC component options, the name should be kebab‑case.
- vue/component-definition-name-casing: 'kebab-case' — For component definitions in script, enforce kebab‑case names.$3
Strict mode is enabled by default. No configuration is required in your app.
Optional type checking script (install
vue-tsc in your app):`bash
npm i -D vue-tsc
``json
{
"scripts": {
"typecheck": "nuxt prepare && vue-tsc -b --noEmit"
}
}
`$3
To run the same checks in your app’s GitHub repository:
1) Copy workflow files into your app’s repo
- Create
.github/workflows/lint.yml with a job that checks out your code, sets up Node, installs dependencies, and runs npm run lint.
- (Optional) Create .github/workflows/typecheck.yml to run npm run typecheck if you added the script.Minimal examples you can adapt:
.github/workflows/lint.yml
`yaml
name: Lint
on:
pull_request:
branches: ['main']
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
`.github/workflows/typecheck.yml
`yaml
name: Typecheck
on:
pull_request:
branches: ['main']
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run typecheck
`2) Configure GitHub as needed
- Enable Actions in your repository settings (if disabled).
- If your app installs private packages, set appropriate repository secrets (e.g.,
NPM_TOKEN, organization PATs) and reference them in the workflow. For public packages only, no extra secrets are required.3) Open a pull request to see the checks run.
—
For maintainers: Working on this layer
$3
- Layer root:
layers/base
- Nuxt 4 app directories are used inside the layer:
- app/plugins/... (e.g., app/plugins/loggly.client.ts)
- app/composables/... (e.g., app/composables/useLogger.ts; deprecated wrapper: app/composables/useLoggly.ts)$3
`bash
From layers/base
npm install
npm run dev # start a dev server for the layer (via Nuxt)
npm run lint # ESLint (with nuxt prepare)
npm run lint:fix # ESLint --fix
npm run typecheck # vue-tsc
`$3
- TypeScript strict mode is enforced via
nuxt.config.ts
- ESLint is provided by @nuxt/eslint with stylistic rules enabled and custom rules in eslint.config.mjs$3
- Client plugin:
app/plugins/loggly.client.ts hooks into Nuxt/Vue error streams and window events and posts to /api/log
- Composable: app/composables/useLogger.ts exposes error|warn|info|debug helpers posting to /api/log (deprecated wrapper useLoggly.ts delegates to useLogger())
- Server plugin: server/plugins/loggly.ts hooks Nitro error events and posts to /api/log
- API endpoint (canonical): server/api/log.post.ts scrubs/normalizes payloads and forwards to Loggly using LOGGLY_TOKEN
- API endpoint (alias, deprecated): server/api/loggly.post.ts re-exports the canonical handler so /api/loggly continues to work
- Runtime config (see nuxt.config.ts):
- Server-only: logglyToken, logglyEndpoint
- Public: logglyEnabled, logglyTags, logLevel
- Supports both LOGGLY_ and NUXT_PUBLIC_LOG_ env vars where appropriate$3
-
layers/base/.env is only for developing the base layer directly (not used by consuming apps)
- For the repo playground, use playground/.env
- For downstream apps, use .env or .env.local in the app’s root$3
There is a simple playground app at
playground that extends the base layer and exposes UI to trigger logging. This is useful for end‑to‑end verification during development.Run from repo root:
`bash
cd playground
npm run dev
`$3
This package is published via GitHub Actions when you push a tag that matches
base-vX.Y.Z.High-level flow:
- Bump the version in
layers/base/package.json (SemVer)
- Commit and push to main
- Create and push a tag base-vX.Y.Z matching the version
- CI checks whether the version exists on npm and publishes if notImportant notes:
- Do NOT rely on
npm version to create the tag (it creates vX.Y.Z). Create base-vX.Y.Z yourself.
- publishConfig.access is set to public; publishes are public to npmjs.
- The workflow skips if the exact version already exists.Step-by-step
1) Bump version without creating a tag:
`bash
cd layers/base
npm version patch --no-git-tag-version # or minor | major
`
2) Commit and push:
`bash
git add layers/base/package.json
git commit -m "chore(base): bump version"
git push origin main
`
3) Tag and push:
`bash
cd layers/base
VERSION=$(node -p "require('./package.json').version")
cd ../..
git tag "base-v$VERSION"
git push origin "base-v$VERSION"
`
4) CI will publish
- Workflow: .github/workflows/publish-base.yml
- Auth: NPM_TOKEN GitHub secretTroubleshooting
- Version exists: bump again and retag
- 401/403: ensure
NPM_TOKEN` is configured and has access