Guides

Migration Guide

Guide for migrating from older versions of Nuxt Crouton

This guide helps you migrate your Nuxt Crouton projects from older versions to the latest release.

Team Authentication Migration (@fyit/crouton-auth)

If you're upgrading from a project that used the useTeamUtility flag or the #crouton/team-auth alias, follow this section.

Breaking Changes Overview

  1. useTeamUtility flag removed from generator
  2. ✅ All collections are now team-scoped by default
  3. #crouton/team-auth alias replaced with @fyit/crouton-auth/server/utils/team imports
  4. @fyit/crouton-auth is now required for team authentication

Breaking Change: useTeamUtility Removed

What Changed

The useTeamUtility configuration flag has been removed. All generated collections now use team-scoped authentication by default.

Before

// crouton.config.js
export default {
  flags: {
    useTeamUtility: true  // or false for non-team collections
  }
}

After

// crouton.config.js
export default {
  // No useTeamUtility flag - all collections are team-scoped
}

Breaking Change: Import Path Changed

What Changed

Generated API endpoints now import team auth utilities from @fyit/crouton-auth/server/utils/team instead of using the #crouton/team-auth alias.

Before (Generated Code)

import { resolveTeamAndCheckMembership } from '#crouton/team-auth'

After (Generated Code)

import { resolveTeamAndCheckMembership } from '@fyit/crouton-auth/server/utils/team'

Migration Steps

Step 1: Install @fyit/crouton-auth

pnpm add @fyit/crouton-auth

Step 2: Update nuxt.config.ts

// nuxt.config.ts
export default defineNuxtConfig({
  extends: [
    '@fyit/crouton',
    '@fyit/crouton-auth'  // Add this if not already present
  ]
})

Step 3: Export Auth Schema

Ensure your database schema exports the auth tables:

// server/database/schema/index.ts
export * from '@fyit/crouton-auth/server/database/schema/auth'

Step 4: Regenerate Collections

Regenerate your collections to update the import paths:

# Backup your customizations first
cp -r layers layers.backup

# Regenerate with new imports
npx crouton-generate config ./crouton.config.js --force

Then restore any customizations from layers.backup/.

Step 5: Update Custom Code

If you have custom API endpoints using the old import:

# Find all old imports
grep -r "#crouton/team-auth" layers/

Replace them:

// Before
import { resolveTeamAndCheckMembership } from '#crouton/team-auth'

// After
import { resolveTeamAndCheckMembership } from '@fyit/crouton-auth/server/utils/team'

Step 6: Update Client-Side Code

If you were using route.params.team directly, switch to useTeamContext():

// Before
const route = useRoute()
const teamId = route.params.team

// After
const { teamId, teamSlug } = useTeamContext()

Step 7: Remove Nitro Alias (if present)

If you had a manual nitro alias, remove it:

// nuxt.config.ts - REMOVE this if present
export default defineNuxtConfig({
  nitro: {
    alias: {
      '#crouton/team-auth': '@fyit/crouton-auth/server/utils/team-auth'  // Remove
    }
  }
})

The @fyit/crouton-auth/server/utils/team import works directly without aliases.

Step 8: Verify and Test

# Type check
npx nuxt typecheck

# Test CRUD operations
pnpm dev

Common Migration Issues

Issue: "Cannot find module '@fyit/crouton-auth/server/utils/team'"

Cause: @fyit/crouton-auth not installed or not extending the layer

Fix:

pnpm add @fyit/crouton-auth

And ensure it's in your extends array in nuxt.config.ts.

Issue: "resolveTeamAndCheckMembership is not exported"

Cause: Using old alias or package version mismatch

Fix: Update to the latest @fyit/crouton-auth:

pnpm update @fyit/crouton-auth

Issue: Team context undefined

Cause: Route doesn't have team parameter

Fix: Ensure your routes include [team] parameter:

pages/
└── [team]/
    └── products/
        └── index.vue

v1.x → v2.0

Version 2.0 introduced significant changes to improve performance and developer experience.

Breaking Changes Overview

  1. send() method removed
  2. ✅ Global state management removed
  3. ✅ Button component API updated
  4. ✅ Cache invalidation improved

Breaking Change 1: send() Removed

What Changed

The send() method from useCrouton() has been removed and replaced with two new approaches:

  • useCroutonMutate() - For quick, one-off mutations
  • useCollectionMutation() - For optimized mutations in forms

Before (v1.x)

<script setup lang="ts">
const { send } = useCrouton()

const handleCreate = async () => {
  await send('create', 'shopProducts', {
    name: 'New Product',
    price: 29.99
  })
}

const handleUpdate = async (id: string) => {
  await send('update', 'shopProducts', {
    id,
    name: 'Updated Name'
  })
}

const handleDelete = async (ids: string[]) => {
  await send('delete', 'shopProducts', ids)
}
</script>

After v2.0 - Option 1: useCroutonMutate (Quick)

For quick actions, toggle buttons, and utility functions:

<script setup lang="ts">
const { mutate } = useCroutonMutate()

const handleCreate = async () => {
  await mutate('create', 'shopProducts', {
    name: 'New Product',
    price: 29.99
  })
}

const handleUpdate = async (id: string) => {
  await mutate('update', 'shopProducts', {
    id,
    name: 'Updated Name'
  })
}

const handleDelete = async (ids: string[]) => {
  await mutate('delete', 'shopProducts', ids)
}
</script>

After v2.0 - Option 2: useCollectionMutation (Optimized)

For forms and repeated operations on the same collection:

<script setup lang="ts">
const { create, update, deleteItems } = useCollectionMutation('shopProducts')

const handleCreate = async () => {
  await create({
    name: 'New Product',
    price: 29.99
  })
}

const handleUpdate = async (id: string, data: any) => {
  await update(id, data)
}

const handleDelete = async (ids: string[]) => {
  await deleteItems(ids)
}
</script>

When to Use Which?

Use CaseUse This
Toggle buttonuseCroutonMutate()
Quick add/removeuseCroutonMutate()
Utility functionuseCroutonMutate()
Generated formsuseCollectionMutation()
Multi-step wizarduseCollectionMutation()
Bulk operationsuseCollectionMutation()

Breaking Change 2: Global State Removed

What Changed

The global reactive collections state (useCollections()) has been removed in favor of useCollectionQuery() with proper caching.

Before (v1.x)

<script setup lang="ts">
const { shopProducts } = useCollections()

// Manual fetch
const { data } = await useFetch('/api/products')
shopProducts.value = data.value

// Manual updates
watch(shopProducts, () => {
  // React to changes
})
</script>

<template>
  <div v-for="product in shopProducts" :key="product.id">
    {{ product.name }}
  </div>
</template>

After v2.0

Query Examples: For complete useCollectionQuery patterns including filters, pagination, and sorting, see Querying Data.
<script setup lang="ts">
// Automatic fetching and caching
const { items, pending, refresh } = await useCollectionQuery('shopProducts')

// Automatic refetch after mutations
const { create } = useCollectionMutation('shopProducts')
await create({ name: 'New Product' })
// → items automatically updates
</script>

<template>
  <div v-if="pending">Loading...</div>
  <div v-else>
    <div v-for="product in items" :key="product.id">
      {{ product.name }}
    </div>
  </div>
</template>

Benefits

  • ✅ Automatic cache management
  • ✅ Automatic refetching after mutations
  • ✅ Loading and error states built-in
  • ✅ No manual state synchronization

Breaking Change 3: Button Component Updated

What Changed

CroutonFormActionButton no longer accepts a @submit handler. Form submission is now handled by the UForm component.

Before (v1.x)

<template>
  <UForm :state="state" :schema="schema">
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" />
    </UFormField>

    <CroutonFormActionButton
      :action="action"
      :collection="collection"
      @submit="send(action, collection, state)"
    />
  </UForm>
</template>

<script setup lang="ts">
const { send } = useCrouton()
const state = ref({ name: '' })
</script>

After v2.0

<template>
  <UForm
    :state="state"
    :schema="schema"
    @submit="handleSubmit"
  >
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" />
    </UFormField>

    <CroutonFormActionButton
      :action="action"
      :collection="collection"
      :loading="loading"
      type="submit"
    />
  </UForm>
</template>

<script setup lang="ts">
const props = defineProps<{
  action: 'create' | 'update' | 'delete'
  collection: string
}>()

const { create, update, deleteItems } = useCollectionMutation(props.collection)
const state = ref({ name: '' })

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>

Migration Steps

Step 1: Update Dependencies

# Update to latest version
pnpm update @fyit/crouton
pnpm update @fyit/crouton-cli
pnpm update @fyit/crouton-i18n  # If using i18n

Step 2: Backup Generated Code

# Create backup of your customizations
cp -r layers layers.backup

Step 3: Update Generated Forms

Option A: Regenerate (Recommended for standard forms)

# Regenerate all collections
npx crouton-generate config ./crouton.config.js --force

Then restore any customizations from layers.backup/.

Option B: Manual Update (If heavily customized)

Update each Form.vue manually:

  1. Replace useCrouton().send() with useCollectionMutation()
  2. Add handleSubmit function
  3. Update CroutonFormActionButton props

See Breaking Change 1 and Breaking Change 3 for examples.

Step 4: Update Custom Code

Find and replace all instances of send():

# Find all usages
grep -r "const.*send.*=.*useCrouton" layers/

# Replace with useCroutonMutate or useCollectionMutation

Quick actions:

// Before
const { send } = useCrouton()
await send('update', 'products', data)

// After
const { mutate } = useCroutonMutate()
await mutate('update', 'products', data)

Forms:

// Before
const { send } = useCrouton()
await send('create', 'products', data)

// After
const { create } = useCollectionMutation('products')
await create(data)

Step 5: Update Global State Usage

Replace useCollections() with useCollectionQuery():

<!-- Before -->
<script setup>
const { shopProducts } = useCollections()
</script>

<!-- After -->
<script setup>
const { items: shopProducts } = await useCollectionQuery('shopProducts')
</script>

See Querying Data for advanced query patterns.

Step 6: Test Thoroughly

  1. Test CRUD operations:
    • Create new items
    • Update existing items
    • Delete items
    • Verify data refreshes automatically
  2. Test forms:
    • Open create form
    • Open edit form
    • Submit forms
    • Check validation
  3. Test caching:
    • Verify data loads from cache
    • Verify cache invalidates after mutations
    • Check multiple views update together

Step 7: Clean Up

# Remove backup if everything works
rm -rf layers.backup

# Clear caches
rm -rf .nuxt node_modules/.cache

# Restart dev server
pnpm dev

Common Migration Issues

Issue: "send is not a function"

Cause: Using old form with new core library

Fix: Regenerate form or manually update (see Step 3)

Issue: Data not refreshing

Cause: Old cache invalidation logic

Fix: Ensure using useCollectionMutation() which handles invalidation automatically

Issue: Type errors

Cause: Type definitions changed

Fix:

rm -rf .nuxt
npx nuxt prepare
# Restart TS server in VS Code

Issue: Forms not submitting

Cause: Missing @submit handler on UForm

Fix: Add @submit="handleSubmit" to UForm component


Need Help?

If you encounter issues during migration:

  1. Check GitHub Issues: github.com/pmcp/nuxt-crouton/issues
  2. Search Discussions: github.com/pmcp/nuxt-crouton/discussions
  3. Create an Issue: Include:
    • Version you're migrating from
    • Version you're migrating to
    • Error messages
    • Code samples