diff --git a/locales/index.d.ts b/locales/index.d.ts
index 3dbe46c7b2..01bec41d9e 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -8830,6 +8830,14 @@ export interface Locale extends ILocale {
              * ボタン
              */
             "button": string;
+            /**
+             * 動的ブロック
+             */
+            "dynamic": string;
+            /**
+             * このブロックは廃止されています。今後は{play}を利用してください。
+             */
+            "dynamicDescription": ParameterizedString<"play">;
             /**
              * ノート埋め込み
              */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index aa765d1310..4ba9ea0221 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2331,6 +2331,8 @@ _pages:
     section: "セクション"
     image: "画像"
     button: "ボタン"
+    dynamic: "動的ブロック"
+    dynamicDescription: "このブロックは廃止されています。今後は{play}を利用してください。"
 
     note: "ノート埋め込み"
     _note:
@@ -2625,4 +2627,3 @@ _mediaControls:
   pip: "ピクチャインピクチャ"
   playbackRate: "再生速度"
   loop: "ループ再生"
-  
\ No newline at end of file
diff --git a/packages/frontend/src/components/page/page.block.vue b/packages/frontend/src/components/page/page.block.vue
index 164720ac6b..c7f72dce8c 100644
--- a/packages/frontend/src/components/page/page.block.vue
+++ b/packages/frontend/src/components/page/page.block.vue
@@ -14,6 +14,7 @@ import XText from './page.text.vue';
 import XSection from './page.section.vue';
 import XImage from './page.image.vue';
 import XNote from './page.note.vue';
+import XDynamic from './page.dynamic.vue';
 
 function getComponent(type: string) {
 	switch (type) {
@@ -21,6 +22,20 @@ function getComponent(type: string) {
 		case 'section': return XSection;
 		case 'image': return XImage;
 		case 'note': return XNote;
+
+		// 動的ページの代替用ブロック
+		case 'button':
+		case 'if':
+		case 'textarea':
+		case 'post':
+		case 'canvas':
+		case 'numberInput':
+		case 'textInput':
+		case 'switch':
+		case 'radioButton':
+		case 'counter':
+			return XDynamic;
+
 		default: return null;
 	}
 }
diff --git a/packages/frontend/src/components/page/page.dynamic.vue b/packages/frontend/src/components/page/page.dynamic.vue
new file mode 100644
index 0000000000..8c511a690d
--- /dev/null
+++ b/packages/frontend/src/components/page/page.dynamic.vue
@@ -0,0 +1,43 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<!-- 動的ページのブロックの代替。利用できないということを表示する -->
+<template>
+<div :class="$style.root">
+	<div :class="$style.heading"><i class="ti ti-dice-5"></i> {{ i18n.ts._pages.blocks.dynamic }}</div>
+	<I18n :src="i18n.ts._pages.blocks.dynamicDescription" tag="div" :class="$style.text">
+		<template #play>
+			<MkA to="/play" class="_link">Play</MkA>
+		</template>
+	</I18n>
+</div>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+
+const props = defineProps<{
+	block: Misskey.entities.PageBlock,
+	page: Misskey.entities.Page,
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	border: 1px solid var(--divider);
+	border-radius: var(--radius);
+	padding: var(--margin);
+	text-align: center;
+}
+
+.heading {
+	font-weight: 700;
+}
+
+.text {
+	font-size: 90%;
+}
+</style>
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index 4e501bd699..e0c7956f6e 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div class="_gaps" :class="$style.textRoot">
 	<Mfm :text="block.text ?? ''" :isNote="false"/>
-	<div v-if="isEnabledUrlPreview">
+	<div v-if="isEnabledUrlPreview" class="_gaps_s">
 		<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 	</div>
 </div>