diff --git a/packages/canvas/DesignCanvas/src/DesignCanvas.vue b/packages/canvas/DesignCanvas/src/DesignCanvas.vue
index 129b57a55..27b47bb83 100644
--- a/packages/canvas/DesignCanvas/src/DesignCanvas.vue
+++ b/packages/canvas/DesignCanvas/src/DesignCanvas.vue
@@ -247,6 +247,24 @@ export default {
       })
     })
 
+    function updatePreviewId(previewId, replace = false) {
+      const url = new URL(window.location.href)
+      if (previewId) {
+        if (previewId === url.searchParams.get('previewid')) {
+          return
+        }
+        url.searchParams.set('previewid', previewId)
+      } else {
+        url.searchParams.delete('previewid')
+      }
+      if (replace) {
+        window.history.replaceState({}, '', url)
+      } else {
+        window.history.pushState({}, '', url)
+      }
+      usePage().postLocationHistoryChanged({ previewId })
+    }
+
     return {
       removeNode,
       canvasSrc,
@@ -264,6 +282,7 @@ export default {
         getPageAncestors: usePage().getAncestors,
         getBaseInfo: () => getMetaApi(META_SERVICE.GlobalService).getBaseInfo(),
         addHistoryDataChangedCallback,
+        updatePreviewId,
         ast,
         getBlockByName: useMaterial().getBlockByName,
         useModal,
diff --git a/packages/canvas/render/src/RenderMain.ts b/packages/canvas/render/src/RenderMain.ts
index 908fb77d8..fa1d08d90 100644
--- a/packages/canvas/render/src/RenderMain.ts
+++ b/packages/canvas/render/src/RenderMain.ts
@@ -28,6 +28,7 @@ import { getPageAncestors } from './material-function/page-getter'
 import CanvasEmpty from './canvas-function/CanvasEmpty.vue'
 import { setCurrentPage } from './canvas-function/page-switcher'
 import { useThrottleFn } from '@vueuse/core'
+import { useRouterPreview } from './canvas-function/router-preview'
 
 // global-context singleton
 const { context: globalContext, setContext: setGlobalContext } = useContext()
@@ -135,7 +136,7 @@ export default defineComponent({
     pageContext.setCssScopeId(props.cssScopeId || `data-te-page-${pageContext.pageId}`)
     if (props.entry) {
       provide('page-ancestors', pageAncestors)
-
+      provide('page-preview', useRouterPreview().previewPath)
       const updatePageAncestor = () => {
         if (routerViewSetting.viewMode === 'standalone') {
           pageAncestors.value = []
diff --git a/packages/canvas/render/src/canvas-function/router-preview.ts b/packages/canvas/render/src/canvas-function/router-preview.ts
new file mode 100644
index 000000000..c04b54ff9
--- /dev/null
+++ b/packages/canvas/render/src/canvas-function/router-preview.ts
@@ -0,0 +1,79 @@
+import { onUnmounted, ref } from 'vue'
+import { getController } from './controller'
+import { getPageAncestors } from '../material-function/page-getter'
+
+export function useRouterPreview() {
+  const pageId = ref(getController().getBaseInfo().pageId)
+  const previewId = ref(getController().getBaseInfo().previewId)
+  const updatePreviewId = getController().updatePreviewId
+  const previewFullPath = ref([])
+  const previewPath = ref([])
+
+  async function calcNewPreviewFullPath() {
+    if (!pageId.value) {
+      if (previewId.value) {
+        updatePreviewId(undefined, true)
+        return
+      }
+      previewFullPath.value = []
+      previewPath.value = []
+      return
+    }
+    if (!previewId.value) {
+      previewFullPath.value = await getPageAncestors(pageId.value)
+      previewPath.value = []
+      return
+    }
+
+    if (previewFullPath.value[previewFullPath.value.length - 1] !== previewId.value) {
+      // previewId changed
+      const fullPath = await getPageAncestors(previewId.value)
+      if (fullPath.includes(pageId.value)) {
+        previewFullPath.value = fullPath
+      } else {
+        updatePreviewId(pageId.value, true)
+      }
+    }
+
+    if (previewFullPath.value.includes(pageId.value)) {
+      // only pageId changed and fast move
+      const fastJumpIndex = previewFullPath.value.indexOf(pageId.value)
+      if (fastJumpIndex + 1 < previewFullPath.value.length) {
+        previewPath.value = previewFullPath.value.slice(fastJumpIndex + 1)
+      } else {
+        previewPath.value = []
+      }
+    } else {
+      previewFullPath.value = await getPageAncestors(pageId.value)
+      previewPath.value = []
+      updatePreviewId(pageId.value, true)
+    }
+  }
+
+  const cancel = getController().addHistoryDataChangedCallback(() => {
+    const newPageId = getController().getBaseInfo().pageId
+    const newPreviewId = getController().getBaseInfo().previewId
+    const pageIdChanged = newPageId !== pageId.value
+    const previewIdChanged = newPreviewId !== previewId.value
+    if (pageIdChanged) {
+      pageId.value = newPageId
+    }
+    if (previewIdChanged) {
+      previewId.value = newPreviewId
+    }
+    if (previewIdChanged || pageIdChanged) {
+      calcNewPreviewFullPath()
+    }
+  })
+
+  onUnmounted(() => {
+    cancel()
+  })
+
+  calcNewPreviewFullPath()
+
+  return {
+    previewPath,
+    previewFullPath
+  }
+}
diff --git a/packages/canvas/render/src/render.ts b/packages/canvas/render/src/render.ts
index 95ec2a3a7..e54c48e11 100644
--- a/packages/canvas/render/src/render.ts
+++ b/packages/canvas/render/src/render.ts
@@ -212,11 +212,13 @@ const getChildren = (schema, mergeScope, pageContext) => {
 }
 function getRenderPageId(currentPageId, isPageStart) {
   const pagePathFromRoot = (inject('page-ancestors') as Ref<any[]>).value
+  const pagePreviewFromCurrentPageChild = (inject('page-preview') as Ref<any[]>).value
+  const fullPath = [...pagePathFromRoot, ...pagePreviewFromCurrentPageChild]
 
   function getNextChild(currentPageId) {
-    const index = pagePathFromRoot.indexOf(currentPageId)
-    if (index > -1 && index + 1 < pagePathFromRoot.length) {
-      return pagePathFromRoot[index + 1]
+    const index = fullPath.indexOf(currentPageId)
+    if (index > -1 && index + 1 < fullPath.length) {
+      return fullPath[index + 1]
     }
     return null
   }
@@ -256,7 +258,13 @@ export const renderer = defineComponent({
       if (renderPageId) {
         return h(getPage(renderPageId), {
           key: ancestors,
-          [DESIGN_TAGKEY]: `${componentName}`
+          [DESIGN_TAGKEY]: `${componentName}`,
+          ...(pageContext.active && !isPageStart
+            ? {
+                [DESIGN_UIDKEY]: schema.id,
+                draggable: true
+              }
+            : {})
         })
       }
     }
diff --git a/packages/common/composable/defaultGlobalService.js b/packages/common/composable/defaultGlobalService.js
index edb09983c..9b38de54b 100644
--- a/packages/common/composable/defaultGlobalService.js
+++ b/packages/common/composable/defaultGlobalService.js
@@ -6,6 +6,7 @@ const getBaseInfo = () => {
   const id = paramsMap.get('id')
   const blockId = paramsMap.get('blockid')
   const pageId = paramsMap.get('pageid')
+  const previewId = paramsMap.get('previewid')
   const type = paramsMap.get('type')
   const version = paramsMap.get('version')
 
@@ -13,6 +14,7 @@ const getBaseInfo = () => {
     type: type || 'app',
     id,
     pageId,
+    previewId,
     blockId,
     version
   }