Your understanding of the use case is correct — keepalive: true in definePageMeta is exactly meant to preserve page state across route changes. However, there is a known bug in Nuxt 4 that explains why it doesn't work in your scenario.
The Bug
In Nuxt 4, KeepAlive only works correctly for a page if all participating routes also have keepalive: true. If you navigate from Page A (with keepalive: true) to Page B (without keepalive), and then go back, Page A will be re-mounted instead of restored from cache. [GitHub Issue #34522]
This has been confirmed as a bug and a fix has been merged (PR #34526), which introduces a cache of pages kept alive via definePageMeta and prevents the <KeepAlive> component from being removed when it's needed. [fix PR #34526]
Workaround (until the fix is released)
As a temporary workaround, add keepalive: true to all pages involved in the navigation (including the destination pages like Page B):
<!-- pages/page-b.vue -->
<script setup lang="ts">
definePageMeta({
keepalive: true,
})
</script>
Additional Tip for Nuxt 4
If you're using Nuxt 4 and want to use include/exclude with <KeepAlive>, be aware that Vue assigns component names based on filenames by default (e.g., both pages/foo/index.vue and pages/bar/index.vue would be named index), making name-based filtering unreliable. You can enable the normalizePageNames experimental feature to fix this: [normalizePageNames docs]
export default defineNuxtConfig({
experimental: {
normalizePageNames: true,
},
})
Then use it like: