Troubleshooting
useCollectionQuery patterns (basic, filtering, pagination, sorting, relations), see Querying Data.This guide covers common issues you might encounter when using Nuxt Crouton and how to resolve them.
Data Not Updating After Save
Problem
Table or list doesn't refresh after creating, updating, or deleting items.
Solution
Check that cache invalidation is working properly:
// In useCollectionMutation
const invalidateCache = async () => {
await refreshNuxtData(`collection:${collection}:{}`)
}
Debug Steps
- Check the cache key format in your console (should show
collection:shopProducts:{}) - Verify the mutation is calling invalidation:
const { create } = useCollectionMutation('shopProducts')
// Should automatically invalidate cache after success
await create({ name: 'New Product' })
- If using custom query parameters, ensure invalidation matches:
// All queries with shopProducts prefix should invalidate
await refreshNuxtData((key) => key.startsWith(`collection:shopProducts:`))
Common Causes
- Collection name mismatch between query and mutation
- Custom cache keys not being invalidated
- API endpoint not returning updated data
Hot Reload Not Working
Problem
Changes to core library files in node_modules/@friendlyinternet/nuxt-crouton don't trigger hot reload.
Solution
Configure Vite to watch the core library:
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
server: {
watch: {
ignored: ['!**/node_modules/@friendlyinternet/**']
}
},
optimizeDeps: {
exclude: ['@friendlyinternet/nuxt-crouton']
}
}
})
Then Restart
# Kill dev server and restart
pnpm dev
Additional Steps
If the issue persists:
- Clear Nuxt cache:
rm -rf .nuxt
- Clear Vite cache:
rm -rf node_modules/.cache
- Restart dev server:
pnpm dev
Tailwind Classes Not Working
Problem
Tailwind CSS classes aren't being applied to Nuxt Crouton layer components:
- Hover states don't work (e.g.,
hover:bg-primary) - Dynamic classes not applying
- Only static classes from your app work, not from layer components
- Classes work when used directly in your app, but not in layer components
Cause
This is expected behavior with Tailwind CSS v4 and Nuxt Layers. Tailwind's JIT compiler doesn't automatically scan components in external layers located in node_modules. This is not a bug—it's how Tailwind v4 is designed to work.
Solution
Add the @source directive to your app's main CSS file to explicitly tell Tailwind to scan the layer components.
1. Create or update your CSS file (e.g., app/assets/css/tailwind.css):
@import "tailwindcss";
@import "@nuxt/ui";
/* Scan Nuxt Crouton layers */
@source "../../../node_modules/@friendlyinternet/nuxt-crouton*/app/**/*.{vue,js,ts}";
2. Adjust the path based on your CSS file location:
- If CSS is at
app/assets/css/tailwind.css: use"../../../node_modules/..." - If CSS is at
app.css: use"../node_modules/..."
3. Restart your dev server:
pnpm dev
Important Notes
- Use relative paths only - Nuxt aliases like
~~,~, or@do NOT work in@sourcedirectives - Be specific - Don't use
@source "../../node_modules/.c12/"as it's too broad and will cause timeouts - Use wildcards - The pattern
nuxt-crouton*/scans all Nuxt Crouton layers automatically
Verify It's Working
Test with a simple hover effect:
<template>
<div class="p-4 bg-gray-100 hover:bg-primary transition-colors">
Hover over me
</div>
</template>
If the hover effect works after adding @source, the configuration is correct.
Alternative: List Layers Explicitly
Instead of the wildcard pattern, you can list each layer:
@source "../../../node_modules/@friendlyinternet/nuxt-crouton/app/**/*.{vue,js,ts}";
@source "../../../node_modules/@friendlyinternet/nuxt-crouton-i18n/app/**/*.{vue,js,ts}";
@source "../../../node_modules/@friendlyinternet/nuxt-crouton-editor/app/**/*.{vue,js,ts}";
@source "../../../node_modules/@friendlyinternet/nuxt-crouton-assets/app/**/*.{vue,js,ts}";
@source "../../../node_modules/@friendlyinternet/nuxt-crouton-supersaas/app/**/*.{vue,js,ts}";
Related Issues
This is a known limitation of Tailwind CSS v4 with Nuxt Layers:
See Installation Guide - Configure Tailwind CSS for complete setup instructions.
Form Not Opening
Problem
Clicking a button to open a form does nothing. No modal or slideover appears.
Debug Steps
- Check if
useCroutonstate is updating:
<script setup>
const { open, showCrouton } = useCrouton()
const handleClick = () => {
console.log('Before:', showCrouton.value)
open('create', 'shopProducts')
console.log('After:', showCrouton.value)
}
</script>
- Verify collection is registered in
app.config.ts:
// app.config.ts
export default defineAppConfig({
croutonCollections: {
shopProducts: {
name: 'shopProducts',
layer: 'shop',
componentName: 'ShopProductsForm',
apiPath: 'shop-products',
}
}
})
- Check component naming matches:
layers/shop/components/products/Form.vue
↓
Component name: ShopProductsForm (PascalCase)
Common Causes
- Collection not registered in
app.config.ts - Typo in collection name (case-sensitive)
- Form component doesn't exist or has wrong name
- Modal container component not imported
Solution
Verify the full chain:
- Collection registered: ✅
- Component exists: ✅
layers/shop/components/products/Form.vue - Component name matches: ✅
ShopProductsForm - Container component imported: ✅ Check app layout
Type Errors After Generation
Problem
TypeScript shows errors like "Cannot find module" or "Property does not exist" after generating a collection.
Solution 1: Restart TypeScript Server
In VS Code:
- Press
Cmd+Shift+P(Mac) orCtrl+Shift+P(Windows/Linux) - Type "Restart TS Server"
- Select "TypeScript: Restart TS Server"
Solution 2: Clear Nuxt Cache
rm -rf .nuxt
npx nuxt prepare
Solution 3: Regenerate Types
npx nuxt typecheck
Common Type Errors
Missing Module Error
Cannot find module '~/layers/shop/types/products'
Fix: Ensure the file exists and path is correct
ls layers/shop/types/products.ts
Property Does Not Exist
Property 'name' does not exist on type 'never'
Fix: Add type parameter to query
// ❌ Bad
const { items } = await useCollectionQuery('shopProducts')
// ✅ Good
import type { ShopProduct } from '~/layers/shop/types/products'
const { items } = await useCollectionQuery<ShopProduct>('shopProducts')
Full Reset Procedure
If all else fails:
# 1. Clear all caches
rm -rf .nuxt node_modules/.cache
# 2. Reinstall dependencies
pnpm install
# 3. Regenerate types
npx nuxt prepare
# 4. Restart dev server
pnpm dev
Duplicate Key Errors During Build
Problem
Getting duplicate key errors when building or running the development server:
[esbuild] (schema.ts:8:2) Duplicate key "userId" in object literal
[esbuild] (schema.ts:9:2) Duplicate key "teamId" in object literal
[esbuild] (schema.ts:10:2) Duplicate key "createdAt" in object literal
[esbuild] (schema.ts:11:2) Duplicate key "updatedAt" in object literal
Cause
You've manually defined auto-generated fields (teamId, userId, createdAt, or updatedAt) in your schema JSON files. The generator adds these fields automatically based on your configuration flags, so manual definitions create duplicates.
Solution 1: Remove Fields from Schema
Remove these fields from your schema JSON files:
{
"userId": { // ❌ Remove this
"type": "string",
"refTarget": "users"
},
"teamId": { // ❌ Remove this
"type": "string"
},
"createdAt": { // ❌ Remove this
"type": "date"
},
"updatedAt": { // ❌ Remove this
"type": "date"
},
"title": { // ✅ Keep your custom fields
"type": "string"
}
}
Solution 2: Adjust Configuration Flags
If you don't need certain auto-generated fields, disable them in your config:
// crouton.config.js
export default {
flags: {
useTeamUtility: false, // Disables teamId & userId
useMetadata: false // Disables createdAt & updatedAt
}
}
Understanding Auto-Generated Fields
The generator automatically adds fields based on configuration:
Always added:
id- Primary key
When useTeamUtility: true:
teamId- Team/organization referenceuserId- User who created the record
When useMetadata: true (default):
createdAt- Creation timestampupdatedAt- Last update timestamp
After Fixing
- Update your schema JSON files to remove auto-generated fields
- Regenerate the collection:
npx crouton-generate config ./crouton.config.js --force
- Restart your dev server:
pnpm dev
See Schema Format - Auto-Generated Fields for complete details.
Delete Button Error
Problem
Error: send is not a function when clicking delete button.
Cause
Using old generated code with the new core library. The send() method was removed in v2.0.
Solution 1: Regenerate Form
npx crouton-generate shop products --fields-file schema.json --force
Solution 2: Manual Update
Update your Form.vue manually:
Before (v1.x):
<script setup>
const { send } = useCrouton()
const handleSubmit = async () => {
await send('create', 'shopProducts', state.value)
}
</script>
After (v2.0):
<script setup>
const { create, update, deleteItems } = useCollectionMutation('shopProducts')
const handleSubmit = async () => {
if (props.action === 'create') {
await create(state.value)
} else if (props.action === 'update') {
await update(state.value.id, state.value)
} else if (props.action === 'delete') {
await deleteItems(props.items)
}
close()
}
</script>
See Migration Guide for complete migration instructions.
Cache Not Invalidating
Problem
Multiple views of the same data, but only one updates after mutation.
Debug
Check if you're using different cache keys:
// List view
const { items } = await useCollectionQuery('shopProducts', {
query: computed(() => ({ page: 1 })) // Cache key: collection:shopProducts:{"page":1}
})
// Detail view
const { items } = await useCollectionQuery('shopProducts', {
query: computed(() => ({ page: 2 })) // Cache key: collection:shopProducts:{"page":2}
})
Solution
useCollectionMutation already invalidates all matching queries:
// This invalidates ALL shopProducts queries, regardless of parameters
await refreshNuxtData((key) => key.startsWith(`collection:shopProducts:`))
If you have custom invalidation, ensure it matches all variants:
// ❌ Bad - only invalidates exact match
await refreshNuxtData(`collection:shopProducts:{}`)
// ✅ Good - invalidates all variants
await refreshNuxtData((key) => key.startsWith(`collection:shopProducts:`))
Validation Errors Not Showing
Problem
Form submits even with invalid data, or validation errors don't display.
Check Schema Setup
// composables/useProducts.ts
import { z } from 'zod'
export function useShopProducts() {
const schema = z.object({
name: z.string().min(1, 'Name is required'),
price: z.number().min(0, 'Price must be positive')
})
return { schema }
}
Check Form Usage
<template>
<UForm
:state="state"
:schema="schema"
@submit="handleSubmit"
>
<UFormField label="Name" name="name">
<UInput v-model="state.name" />
</UFormField>
</UForm>
</template>
<script setup lang="ts">
const { schema } = useShopProducts()
const state = ref({ name: '', price: 0 })
</script>
Common Issues
- Schema not passed to form:
- Ensure
:schema="schema"prop is set
- Ensure
- Field name mismatch:
name="name"must match schema key- Check spelling and case
- State not reactive:
- Use
ref()orreactive()for form state
- Use
Layer Pages 404 Errors
Problem
Getting 404 errors when navigating to Nuxt Crouton pages like /dashboard/[team]/crouton:
404 - Page not found: /dashboard/myteam/crouton
Cause
You're using Nuxt 3 instead of Nuxt 4. Nuxt Crouton uses the app/pages/ directory structure (Nuxt 4 convention). When the consuming app uses Nuxt 3, Nuxt looks for pages in pages/ instead of app/pages/, so layer pages are not found.
Solution
Upgrade your project to Nuxt 4:
{
"dependencies": {
"nuxt": "^4.0.0",
"@nuxt/ui": "^4.0.0"
}
}
Then reinstall dependencies:
pnpm install
Verification
After upgrading, verify your project structure follows Nuxt 4 conventions:
app/
├── pages/ # Nuxt 4 location
├── components/
├── composables/
└── app.vue
API Route 404 Errors
Problem
Getting 404 errors when trying to fetch or mutate data.
Check API Route Paths
Ensure your API routes match the collection's apiPath:
// app.config.ts
export default defineAppConfig({
croutonCollections: {
shopProducts: {
apiPath: 'shop-products', // ← This determines the route
}
}
})
Should match:
server/api/teams/[team]/shop-products/
├── index.get.ts # GET /api/teams/:team/shop-products
├── index.post.ts # POST /api/teams/:team/shop-products
├── [id].patch.ts # PATCH /api/teams/:team/shop-products/:id
└── [id].delete.ts # DELETE /api/teams/:team/shop-products/:id
Check Team-Based Routes
If using team-based auth:
// server/api/teams/[team]/shop-products/index.get.ts
export default defineEventHandler(async (event) => {
const teamId = getRouterParam(event, 'team')
if (!teamId) {
throw createError({ statusCode: 400, message: 'Team ID required' })
}
// Your query logic
})
Debug API Calls
Check browser DevTools Network tab:
- Look for failed requests
- Check request URL matches expected pattern
- Verify request method (GET, POST, PATCH, DELETE)
- Check response error messages
Translation Issues
Problem
Translations not loading or switching locales doesn't update content.
Check i18n Setup
Ensure @friendlyinternet/nuxt-crouton-i18n is included in your nuxt.config.ts extends array.
Check Query Locale Binding
Make sure your query reactively binds to the i18n locale using a computed query parameter (see Querying with Filters for the pattern).
Check Translation Fields
Ensure fields are marked as translatable in config:
// crouton.config.js
export default {
translations: {
collections: {
products: ['name', 'description'] // ← Mark translatable fields
}
}
}
Customizing Auto-Generated Repeater Components
Overview
When you define a repeater field in your schema, the generator automatically creates a placeholder component for you. This placeholder is fully functional but needs customization to match your specific data structure.
What You Get
The auto-generated placeholder component includes:
- Proper Vue component structure (props, emits, v-model)
- TypeScript interface with TODO comments
- Default values in computed getter
- ID field (auto-generated, disabled)
- Debug section showing raw data
- Visual indicators (dashed border, gray background)
Example placeholder location:
layers/bookings/collections/locations/app/components/Slot.vue
How to Customize
Step 1: Update the Interface
Define the fields your repeater item needs:
// Before (placeholder)
interface SlotItem {
id: string
// TODO: Add your fields here
}
// After (customized)
interface SlotItem {
id: string
label: string
startTime: string
endTime: string
}
Step 2: Add Default Values
Provide sensible defaults for your fields:
import { nanoid } from 'nanoid'
// Before
const localValue = computed({
get: () => props.modelValue || {
id: nanoid(),
// TODO: Add default values for your fields
},
set: (val) => emit('update:modelValue', val)
})
// After
const localValue = computed({
get: () => props.modelValue || {
id: nanoid(),
label: '',
startTime: '09:00',
endTime: '17:00'
},
set: (val) => emit('update:modelValue', val)
})
Step 3: Update the Template
Replace the placeholder with your custom form fields:
<template>
<!-- Remove the TODO section and placeholder styling -->
<div class="grid grid-cols-4 gap-4">
<UFormField label="ID" name="id">
<UInput v-model="localValue.id" disabled />
</UFormField>
<UFormField label="Label" name="label">
<UInput v-model="localValue.label" />
</UFormField>
<UFormField label="Start Time" name="startTime">
<UInput v-model="localValue.startTime" type="time" />
</UFormField>
<UFormField label="End Time" name="endTime">
<UInput v-model="localValue.endTime" type="time" />
</UFormField>
</div>
</template>
Complete Customized Example
Before (auto-generated):
<script setup lang="ts">
import { nanoid } from 'nanoid'
interface SlotItem {
id: string
// TODO: Add your fields here
}
const props = defineProps<{ modelValue: SlotItem }>()
const emit = defineEmits<{ 'update:modelValue': [value: SlotItem] }>()
const localValue = computed({
get: () => props.modelValue || { id: nanoid() },
set: (val) => emit('update:modelValue', val)
})
</script>
<template>
<div class="space-y-3 p-4 bg-gray-50 rounded-lg border border-dashed">
<div class="text-sm text-gray-600">
<strong>TODO:</strong> Customize this component
</div>
<!-- Placeholder fields -->
</div>
</template>
After (customized):
<script setup lang="ts">
import { nanoid } from 'nanoid'
interface SlotItem {
id: string
label: string
startTime: string
endTime: string
}
const props = defineProps<{ modelValue: SlotItem }>()
const emit = defineEmits<{ 'update:modelValue': [value: SlotItem] }>()
const localValue = computed({
get: () => props.modelValue || {
id: nanoid(),
label: '',
startTime: '09:00',
endTime: '17:00'
},
set: (val) => emit('update:modelValue', val)
})
</script>
<template>
<div class="grid grid-cols-4 gap-4">
<UFormField label="ID" name="id">
<UInput v-model="localValue.id" disabled />
</UFormField>
<UFormField label="Label" name="label">
<UInput v-model="localValue.label" />
</UFormField>
<UFormField label="Start Time" name="startTime">
<UInput v-model="localValue.startTime" type="time" />
</UFormField>
<UFormField label="End Time" name="endTime">
<UInput v-model="localValue.endTime" type="time" />
</UFormField>
</div>
</template>
Tips
- The placeholder works immediately—you can test your repeater before customizing
- Keep the ID field disabled to prevent accidental modification
- The debug section in the placeholder helps verify data structure during development
- Remove placeholder styling (dashed borders, gray background) when ready for production
See Schema Format - Repeater Fields for more details on defining repeater fields.
Performance Issues
Problem
Slow page loads or laggy UI when working with large datasets.
Solutions
1. Implement Pagination
Use pagination with page and limit query parameters (see Querying with Pagination).
2. Use Server-Side Filtering
// server/api/teams/[team]/products/index.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const { page = 1, limit = 50, search } = query
// Filter in database, not in frontend
let dbQuery = db.select().from(products)
if (search) {
dbQuery = dbQuery.where(like(products.name, `%${search}%`))
}
return dbQuery
.limit(limit)
.offset((page - 1) * limit)
})
3. Optimize Relations
Use server-side joins instead of multiple queries to avoid N+1 query problems. See Querying with Relations for the recommended pattern.
Getting More Help
If you're still experiencing issues:
- Check GitHub Issues: Search for similar problems at github.com/pmcp/nuxt-crouton/issues
- Create an Issue: Include:
- Nuxt Crouton version
- Nuxt version
- Node version
- Error messages
- Minimal reproduction steps
- Ask in Discussions: For general questions, use GitHub Discussions
Related Resources
- Migration Guide - Upgrading from older versions
- Best Practices - Avoid common pitfalls
- API Reference - Detailed API documentation