SSR hydration: Nuxt

The fastest path to server-rendered form values that don't flicker on hydrate. Install the module, write useForm normally, and the round-trip wires itself.

Category
Integration
Setup
modules: [attaform/nuxt]
Transport
nuxtApp.payload (top-level key)
What rides
values · errors · field flags

This page is code-only; SSR happens at the server runtime, and the docs site can't demo a hydration round-trip without bootstrapping a separate server. The setup is small enough to verify in your own Nuxt project; see SSR hydration: bare Vue for the equivalent without the Nuxt convenience.

Nothing to wire

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['attaform/nuxt'],
})
<script setup lang="ts">
  const form = useForm({ schema, key: 'signup' })
</script>

That's the whole setup. Values, errors, and field interaction flags (touched / focused / blurred / connected) survive the server → client round-trip through nuxtApp.payload. Need to peek? Open the rendered HTML and look for the Nuxt payload <script> block; attaform is a top-level key.

What crosses the wire

SurfaceRound-trips?Notes
form.valuesWhole tree, including nested objects and arrays.
errorsEvery entry in the error map, keyed by path.
fieldstouched / focused / blurred / connected / updatedAt per path.
blankPathsNumeric-blank state survives the boundary.
history chainEach tab walks its own undo timeline.
Validation in-flight stateRe-runs locally on the client.

The client-side form is identical to the server one; no second round of validation kicks in unless the user interacts.

Auto-imports

The Nuxt module auto-imports useForm from the unified entry point. No import statement needed in <script setup>:

<script setup lang="ts">
  const form = useForm({ schema, key: 'signup' })
</script>

For the Zod-typed wrapper (useForm from attaform/zod), or for any other helper (injectForm, useWizard, useRegister, parseApiErrors, unset, defaultDisplayState), import explicitly:

<script setup lang="ts">
  import { injectForm, useWizard } from 'attaform/zod'
  import { parseApiErrors } from 'attaform'
</script>

App-wide defaults under Nuxt

The Nuxt module surfaces the same AttaformDefaults you'd pass to createAttaform({ defaults }):

export default defineNuxtConfig({
  modules: ['attaform/nuxt'],
  attaform: {
    defaults: {
      validateOn: 'change',
      debounceMs: 100,
      onInvalidSubmit: 'focus-first-error',
    },
  },
})

See App-wide defaults for the full option list and merge semantics.

Common issues

"The form is empty on the client even though the server rendered values."

  • Does the form's key match between server and client? Hard-code it as a string literal; uuidv4() or Math.random() produces a fresh key per render and breaks the round-trip lookup.
  • Was the form created in setup? Forms created in onMounted or event handlers aren't in the SSR snapshot.

"Field errors from the server disappear on first interaction."

By design. Any mutation re-runs validation, which can replace the errors. To keep server-provided errors around until the user dirties the field, gate the display on form.fields.<path>.touched or form.meta.dirty:

<small v-if="form.errors.email?.[0] && !form.fields.email.touched">
  {{ form.errors.email[0].message }}
</small>

"Some fields look right, others don't."

Forms created in onMounted or event handlers aren't in the SSR snapshot. Create forms during setup so the server sees them.

DevTools panel

The Nuxt module auto-wires the Attaform DevTools panel into the Nuxt DevTools sidebar. The panel inspects every registered form including the SSR-rendered ones, useful for confirming the server-rendered shape matches your expectation before the user touches anything.

Where to next