Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Preserve iframe State Across Vue Route Changes Without Reloading

Tech 1

Vue’s built-in keep-alive caches component VNodes and instances, but it cannot prevent an iframe’s document from reloading. An iframe hosts an independant browsing context; each time its DOM node is mounted, the embedded page is loaded anew. Caching the VNode does not capture the iframe’s internal document, and re-rendering the iframe element is functionally equivalent to opening a new page.

To keep iframe state intact during route navigation, avoid remounting the iframe node. Render iframe components outside of router-view and toggle visibility with v-show so the DOM node persists across route changes.

Baseline pattern

Define routes for iframe paths without assigning a component so router-view does not mount anything for those paths. Render iframe components next to router-view and control visibility with v-show.

main.js:

import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'

Vue.use(VueRouter)

const Home = { template: '<div>Home</div>' }

const routeTable = [
  // No component: handled outside router-view
  { path: '/frame-a', name: 'frame-a' },
  { path: '/frame-b', name: 'frame-b' },
  // Normal routed view
  { path: '/home', component: Home }
]

const router = new VueRouter({ routes: routeTable })

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

App.vue:

<template>
  <div id="app">
    <nav class="nav">
      <router-link class="link" to="/frame-a">Frame A</router-link>
      <router-link class="link" to="/frame-b">Frame B</router-link>
      <router-link class="link" to="/home">Home</router-link>
    </nav>

    <keep-alive>
      <router-view />
    </keep-alive>

    <!-- Persist iframe DOM nodes; switch visibility only -->
    <FrameA v-show="$route.path === '/frame-a'" />
    <FrameB v-show="$route.path === '/frame-b'" />
  </div>
</template>

<script>
import FrameA from './components/FrameA.vue'
import FrameB from './components/FrameB.vue'

export default {
  name: 'App',
  components: { FrameA, FrameB }
}
</script>

Key idea:

  • Do not let router-view mount the iframe routes.
  • Mount iframe components once at the root and toggle thier visibility with v-show to keep their internal state alive.

Generalized and lazy approach

The baseline works but requires manual imports and always renders iframe nodes at app start. Improve it by:

  • Marking iframe routes in the route config with a dedicated field holding the component.
  • Dynamically rendering only iframe components the user has visited (lazy mount).
  • Ecnapsulating the switching logic in to a custom router-view wrapper.

Route configuration

Attach the iframe component to the route record:

main.js:

import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'

import FrameA from './components/FrameA.vue'
import FrameB from './components/FrameB.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/frame-a', name: 'frame-a', iframeComponent: FrameA },
  { path: '/frame-b', name: 'frame-b', iframeComponent: FrameB },
  { path: '/home', component: { template: '<div>Home</div>' } }
]

const router = new VueRouter({ routes })

new Vue({ router, render: h => h(App) }).$mount('#app')

Smart router-view wrapper

Create a component that:

  • Renders normal routes through keep-alive + router-view.
  • Builds a list of iframe routes from the router config.
  • Lazily mounts an iframe component when its route is first visited and keeps it in the DOM, toggling visibility by path.

SmartRouterView.vue:

<template>
  <div>
    <keep-alive>
      <router-view />
    </keep-alive>

    <component
      v-for="entry in mountedIframeEntries"
      :key="entry.key"
      :is="entry.component"
      v-show="$route.path === entry.path"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      iframeEntries: [] // { key, path, opened, component }
    }
  },
  created() {
    this.iframeEntries = this.collectIframeEntries()
    this.markOpened(this.$route)
  },
  watch: {
    $route(to) {
      this.markOpened(to)
    }
  },
  computed: {
    mountedIframeEntries() {
      return this.iframeEntries.filter(e => e.opened)
    }
  },
  methods: {
    collectIframeEntries() {
      const routes = (this.$router && this.$router.options && this.$router.options.routes) || []
      return routes
        .filter(r => r.iframeComponent)
        .map(r => ({
          key: r.name || r.path,
          path: r.path,
          opened: false,
          component: r.iframeComponent
        }))
    },
    markOpened(route) {
      const hit = this.iframeEntries.find(e => e.path === route.path)
      if (hit && !hit.opened) hit.opened = true
    }
  }
}
</script>

Use the wrapper in App.vue:

<template>
  <div id="app">
    <nav class="nav">
      <router-link class="link" to="/frame-a">Frame A</router-link>
      <router-link class="link" to="/frame-b">Frame B</router-link>
      <router-link class="link" to="/home">Home</router-link>
    </nav>

    <SmartRouterView />
  </div>
</template>

<script>
import SmartRouterView from './components/SmartRouterView.vue'

export default {
  name: 'App',
  components: { SmartRouterView }
}
</script>

Notes:

  • iframe routes omit a component in the route record so router-view skips mounting them. The wrapper renders and preserves iframe components independently.
  • Lazy mounting is driven by the opened flag, set the first time the corresponding route becomes active.
  • Visibility siwtching uses v-show so the iframe DOM nodes (and their internal browsing contexts) remain intact across route transitions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.