[{"data":1,"prerenderedAt":744},["ShallowReactive",2],{"content-\u002Fdocs\u002Fserver-and-ssr\u002Fssr-nuxt":3},{"id":4,"title":5,"body":6,"description":722,"extension":723,"meta":724,"metaRows":725,"navigation":738,"path":739,"seo":740,"source":741,"stem":742,"__hash__":743},"docs\u002Fdocs\u002Fserver-and-ssr\u002Fssr-nuxt.md","SSR hydration: Nuxt",{"type":7,"value":8,"toc":709},"minimark",[9,13,25,28,37,42,97,161,176,180,292,295,299,313,357,387,442,446,456,540,548,552,557,587,591,600,655,659,668,672,680,684,705],[10,11,5],"h1",{"id":12},"ssr-hydration-nuxt",[14,15,16],"blockquote",{},[17,18,19,20,24],"p",{},"The fastest path to server-rendered form values that don't flicker on hydrate. Install the module, write ",[21,22,23],"code",{},"useForm"," normally, and the round-trip wires itself.",[26,27],"docs-meta-table",{},[17,29,30,31,36],{},"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 ",[32,33,35],"a",{"href":34},"\u002Fdocs\u002Fserver-and-ssr\u002Fssr-bare-vue","SSR hydration: bare Vue"," for the equivalent without the Nuxt convenience.",[38,39,41],"h2",{"id":40},"nothing-to-wire","Nothing to wire",[43,44,49],"pre",{"className":45,"code":46,"language":47,"meta":48,"style":48},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['attaform\u002Fnuxt'],\n})\n","ts","",[21,50,51,60,78,91],{"__ignoreMap":48},[52,53,56],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sJ8bj","\u002F\u002F nuxt.config.ts\n",[52,61,63,67,70,74],{"class":54,"line":62},2,[52,64,66],{"class":65},"szBVR","export",[52,68,69],{"class":65}," default",[52,71,73],{"class":72},"sScJk"," defineNuxtConfig",[52,75,77],{"class":76},"sVt8B","({\n",[52,79,81,84,88],{"class":54,"line":80},3,[52,82,83],{"class":76},"  modules: [",[52,85,87],{"class":86},"sZZnC","'attaform\u002Fnuxt'",[52,89,90],{"class":76},"],\n",[52,92,94],{"class":54,"line":93},4,[52,95,96],{"class":76},"})\n",[43,98,102],{"className":99,"code":100,"language":101,"meta":48,"style":48},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\n  const form = useForm({ schema, key: 'signup' })\n\u003C\u002Fscript>\n","vue",[21,103,104,128,152],{"__ignoreMap":48},[52,105,106,109,113,116,119,122,125],{"class":54,"line":55},[52,107,108],{"class":76},"\u003C",[52,110,112],{"class":111},"s9eBZ","script",[52,114,115],{"class":72}," setup",[52,117,118],{"class":72}," lang",[52,120,121],{"class":76},"=",[52,123,124],{"class":86},"\"ts\"",[52,126,127],{"class":76},">\n",[52,129,130,133,137,140,143,146,149],{"class":54,"line":62},[52,131,132],{"class":65},"  const",[52,134,136],{"class":135},"sj4cs"," form",[52,138,139],{"class":65}," =",[52,141,142],{"class":72}," useForm",[52,144,145],{"class":76},"({ schema, key: ",[52,147,148],{"class":86},"'signup'",[52,150,151],{"class":76}," })\n",[52,153,154,157,159],{"class":54,"line":80},[52,155,156],{"class":76},"\u003C\u002F",[52,158,112],{"class":111},[52,160,127],{"class":76},[17,162,163,164,167,168,171,172,175],{},"That's the whole setup. Values, errors, and field interaction flags (touched \u002F focused \u002F blurred \u002F connected) survive the server → client round-trip through ",[21,165,166],{},"nuxtApp.payload",". Need to peek? Open the rendered HTML and look for the Nuxt payload ",[21,169,170],{},"\u003Cscript>"," block; ",[21,173,174],{},"attaform"," is a top-level key.",[38,177,179],{"id":178},"what-crosses-the-wire","What crosses the wire",[181,182,183,199],"table",{},[184,185,186],"thead",{},[187,188,189,193,196],"tr",{},[190,191,192],"th",{},"Surface",[190,194,195],{},"Round-trips?",[190,197,198],{},"Notes",[200,201,202,216,228,256,268,282],"tbody",{},[187,203,204,210,213],{},[205,206,207],"td",{},[21,208,209],{},"form.values",[205,211,212],{},"✅",[205,214,215],{},"Whole tree, including nested objects and arrays.",[187,217,218,223,225],{},[205,219,220],{},[21,221,222],{},"errors",[205,224,212],{},[205,226,227],{},"Every entry in the error map, keyed by path.",[187,229,230,235,237],{},[205,231,232],{},[21,233,234],{},"fields",[205,236,212],{},[205,238,239,242,243,242,246,242,249,242,252,255],{},[21,240,241],{},"touched"," \u002F ",[21,244,245],{},"focused",[21,247,248],{},"blurred",[21,250,251],{},"connected",[21,253,254],{},"updatedAt"," per path.",[187,257,258,263,265],{},[205,259,260],{},[21,261,262],{},"blankPaths",[205,264,212],{},[205,266,267],{},"Numeric-blank state survives the boundary.",[187,269,270,276,279],{},[205,271,272,275],{},[21,273,274],{},"history"," chain",[205,277,278],{},"❌",[205,280,281],{},"Each tab walks its own undo timeline.",[187,283,284,287,289],{},[205,285,286],{},"Validation in-flight state",[205,288,278],{},[205,290,291],{},"Re-runs locally on the client.",[17,293,294],{},"The client-side form is identical to the server one; no second round of validation kicks in unless the user interacts.",[38,296,298],{"id":297},"auto-imports","Auto-imports",[17,300,301,302,304,305,308,309,312],{},"The Nuxt module auto-imports ",[21,303,23],{}," from the unified entry point. No ",[21,306,307],{},"import"," statement needed in ",[21,310,311],{},"\u003Cscript setup>",":",[43,314,315],{"className":99,"code":100,"language":101,"meta":48,"style":48},[21,316,317,333,349],{"__ignoreMap":48},[52,318,319,321,323,325,327,329,331],{"class":54,"line":55},[52,320,108],{"class":76},[52,322,112],{"class":111},[52,324,115],{"class":72},[52,326,118],{"class":72},[52,328,121],{"class":76},[52,330,124],{"class":86},[52,332,127],{"class":76},[52,334,335,337,339,341,343,345,347],{"class":54,"line":62},[52,336,132],{"class":65},[52,338,136],{"class":135},[52,340,139],{"class":65},[52,342,142],{"class":72},[52,344,145],{"class":76},[52,346,148],{"class":86},[52,348,151],{"class":76},[52,350,351,353,355],{"class":54,"line":80},[52,352,156],{"class":76},[52,354,112],{"class":111},[52,356,127],{"class":76},[17,358,359,360,362,363,366,367,370,371,370,374,370,377,370,380,370,383,386],{},"For the Zod-typed wrapper (",[21,361,23],{}," from ",[21,364,365],{},"attaform\u002Fzod","), or for any other helper (",[21,368,369],{},"injectForm",", ",[21,372,373],{},"useWizard",[21,375,376],{},"useRegister",[21,378,379],{},"parseApiErrors",[21,381,382],{},"unset",[21,384,385],{},"defaultDisplayState","), import explicitly:",[43,388,390],{"className":99,"code":389,"language":101,"meta":48,"style":48},"\u003Cscript setup lang=\"ts\">\n  import { injectForm, useWizard } from 'attaform\u002Fzod'\n  import { parseApiErrors } from 'attaform'\n\u003C\u002Fscript>\n",[21,391,392,408,422,434],{"__ignoreMap":48},[52,393,394,396,398,400,402,404,406],{"class":54,"line":55},[52,395,108],{"class":76},[52,397,112],{"class":111},[52,399,115],{"class":72},[52,401,118],{"class":72},[52,403,121],{"class":76},[52,405,124],{"class":86},[52,407,127],{"class":76},[52,409,410,413,416,419],{"class":54,"line":62},[52,411,412],{"class":65},"  import",[52,414,415],{"class":76}," { injectForm, useWizard } ",[52,417,418],{"class":65},"from",[52,420,421],{"class":86}," 'attaform\u002Fzod'\n",[52,423,424,426,429,431],{"class":54,"line":80},[52,425,412],{"class":65},[52,427,428],{"class":76}," { parseApiErrors } ",[52,430,418],{"class":65},[52,432,433],{"class":86}," 'attaform'\n",[52,435,436,438,440],{"class":54,"line":93},[52,437,156],{"class":76},[52,439,112],{"class":111},[52,441,127],{"class":76},[38,443,445],{"id":444},"app-wide-defaults-under-nuxt","App-wide defaults under Nuxt",[17,447,448,449,452,453,312],{},"The Nuxt module surfaces the same ",[21,450,451],{},"AttaformDefaults"," you'd pass to ",[21,454,455],{},"createAttaform({ defaults })",[43,457,459],{"className":45,"code":458,"language":47,"meta":48,"style":48},"export default defineNuxtConfig({\n  modules: ['attaform\u002Fnuxt'],\n  attaform: {\n    defaults: {\n      validateOn: 'change',\n      debounceMs: 100,\n      onInvalidSubmit: 'focus-first-error',\n    },\n  },\n})\n",[21,460,461,471,479,484,489,501,512,523,529,535],{"__ignoreMap":48},[52,462,463,465,467,469],{"class":54,"line":55},[52,464,66],{"class":65},[52,466,69],{"class":65},[52,468,73],{"class":72},[52,470,77],{"class":76},[52,472,473,475,477],{"class":54,"line":62},[52,474,83],{"class":76},[52,476,87],{"class":86},[52,478,90],{"class":76},[52,480,481],{"class":54,"line":80},[52,482,483],{"class":76},"  attaform: {\n",[52,485,486],{"class":54,"line":93},[52,487,488],{"class":76},"    defaults: {\n",[52,490,492,495,498],{"class":54,"line":491},5,[52,493,494],{"class":76},"      validateOn: ",[52,496,497],{"class":86},"'change'",[52,499,500],{"class":76},",\n",[52,502,504,507,510],{"class":54,"line":503},6,[52,505,506],{"class":76},"      debounceMs: ",[52,508,509],{"class":135},"100",[52,511,500],{"class":76},[52,513,515,518,521],{"class":54,"line":514},7,[52,516,517],{"class":76},"      onInvalidSubmit: ",[52,519,520],{"class":86},"'focus-first-error'",[52,522,500],{"class":76},[52,524,526],{"class":54,"line":525},8,[52,527,528],{"class":76},"    },\n",[52,530,532],{"class":54,"line":531},9,[52,533,534],{"class":76},"  },\n",[52,536,538],{"class":54,"line":537},10,[52,539,96],{"class":76},[17,541,542,543,547],{},"See ",[32,544,546],{"href":545},"\u002Fdocs\u002Fcross-cutting-state\u002Fapp-defaults","App-wide defaults"," for the full option list and merge semantics.",[38,549,551],{"id":550},"common-issues","Common issues",[553,554,556],"h3",{"id":555},"the-form-is-empty-on-the-client-even-though-the-server-rendered-values","\"The form is empty on the client even though the server rendered values.\"",[558,559,560,576],"ul",{},[561,562,563,564,567,568,571,572,575],"li",{},"Does the form's ",[21,565,566],{},"key"," match between server and client? Hard-code it as a string literal; ",[21,569,570],{},"uuidv4()"," or ",[21,573,574],{},"Math.random()"," produces a fresh key per render and breaks the round-trip lookup.",[561,577,578,579,582,583,586],{},"Was the form created in ",[21,580,581],{},"setup","? Forms created in ",[21,584,585],{},"onMounted"," or event handlers aren't in the SSR snapshot.",[553,588,590],{"id":589},"field-errors-from-the-server-disappear-on-first-interaction","\"Field errors from the server disappear on first interaction.\"",[17,592,593,594,571,597,312],{},"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 ",[21,595,596],{},"form.fields.\u003Cpath>.touched",[21,598,599],{},"form.meta.dirty",[43,601,603],{"className":99,"code":602,"language":101,"meta":48,"style":48},"\u003Csmall v-if=\"form.errors.email?.[0] && !form.fields.email.touched\">\n  {{ form.errors.email[0].message }}\n\u003C\u002Fsmall>\n",[21,604,605,642,647],{"__ignoreMap":48},[52,606,607,609,612,615,617,620,623,626,629,632,635,638,640],{"class":54,"line":55},[52,608,108],{"class":76},[52,610,611],{"class":111},"small",[52,613,614],{"class":65}," v-if",[52,616,121],{"class":76},[52,618,619],{"class":86},"\"",[52,621,622],{"class":76},"form.errors.email?.[",[52,624,625],{"class":135},"0",[52,627,628],{"class":76},"] ",[52,630,631],{"class":65},"&&",[52,633,634],{"class":65}," !",[52,636,637],{"class":76},"form.fields.email.touched",[52,639,619],{"class":86},[52,641,127],{"class":76},[52,643,644],{"class":54,"line":62},[52,645,646],{"class":76},"  {{ form.errors.email[0].message }}\n",[52,648,649,651,653],{"class":54,"line":80},[52,650,156],{"class":76},[52,652,611],{"class":111},[52,654,127],{"class":76},[553,656,658],{"id":657},"some-fields-look-right-others-dont","\"Some fields look right, others don't.\"",[17,660,661,662,664,665,667],{},"Forms created in ",[21,663,585],{}," or event handlers aren't in the SSR snapshot. Create forms during ",[21,666,581],{}," so the server sees them.",[38,669,671],{"id":670},"devtools-panel","DevTools panel",[17,673,674,675,679],{},"The Nuxt module auto-wires the ",[32,676,678],{"href":677},"\u002Fdocs\u002Fdevtools-and-debugging\u002Fdevtools-panel","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.",[38,681,683],{"id":682},"where-to-next","Where to next",[558,685,686,691,698],{},[561,687,688,690],{},[32,689,35],{"href":34},": the same round-trip without the Nuxt module, when you're not on Nuxt.",[561,692,693,697],{},[32,694,696],{"href":695},"\u002Fdocs\u002Fpersistence\u002Foverview","Persistence overview",": different problem (durable drafts vs. one-time SSR snapshot), composable with SSR.",[561,699,700,704],{},[32,701,703],{"href":702},"\u002Fdocs\u002Fserver-and-ssr\u002Fperformance","Performance",": what SSR hydration costs in practice.",[706,707,708],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":48,"searchDepth":62,"depth":62,"links":710},[711,712,713,714,715,720,721],{"id":40,"depth":62,"text":41},{"id":178,"depth":62,"text":179},{"id":297,"depth":62,"text":298},{"id":444,"depth":62,"text":445},{"id":550,"depth":62,"text":551,"children":716},[717,718,719],{"id":555,"depth":80,"text":556},{"id":589,"depth":80,"text":590},{"id":657,"depth":80,"text":658},{"id":670,"depth":62,"text":671},{"id":682,"depth":62,"text":683},"Server-rendered Vue 3 forms round-trip values, errors, and field flags to the client automatically via attaform\u002Fnuxt. No hydration flicker, no manual wiring.","md",{},[726,729,732,735],{"label":727,"value":728},"Category","Integration",{"label":730,"value":731,"kind":21},"Setup","modules: [attaform\u002Fnuxt]",{"label":733,"value":734},"Transport","nuxtApp.payload (top-level key)",{"label":736,"value":737},"What rides","values · errors · field flags",true,"\u002Fdocs\u002Fserver-and-ssr\u002Fssr-nuxt",{"title":5,"description":722},null,"docs\u002Fserver-and-ssr\u002Fssr-nuxt","h5zoCHjStzUkNn_sX1Rv_-KJDndkqpsBK2wvMQ_YSQw",1780949762049]