diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 31d51c25fc..70192a55c7 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2300,8 +2300,10 @@ room:
   exit: "戻る"
   remove: "しまう"
   save: "保存"
+  saved: "保存しました"
   clear: "片付け"
   clear-confirm: "全ての家具をしまいますか?"
+  leave-confirm: "未保存の変更があります、移動しますか?"
   chooseImage: "画像を選択"
   room-type: "部屋のタイプ"
   carpet-color: "床の色"
diff --git a/src/client/app/common/views/pages/room/room.vue b/src/client/app/common/views/pages/room/room.vue
index 2456f8f8e8..1e81920c22 100644
--- a/src/client/app/common/views/pages/room/room.vue
+++ b/src/client/app/common/views/pages/room/room.vue
@@ -42,7 +42,7 @@
 			</label>
 		</section>
 		<section>
-			<ui-button primary @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
+			<ui-button :primary="changed" @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 			<ui-button @click="clear()"><fa :icon="faBroom"/> {{ $t('clear') }}</ui-button>
 		</section>
 	</div>
@@ -87,11 +87,14 @@ export default Vue.extend({
 			isTranslateMode: false,
 			isRotateMode: false,
 			isMyRoom: false,
+			changed: false,
 			faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan, faBroom,
 		};
 	},
 
 	async mounted() {
+		window.addEventListener('beforeunload', this.beforeunload);
+
 		const user = await this.$root.api('users/show', {
 			...parseAcct(this.acct)
 		});
@@ -125,11 +128,37 @@ export default Vue.extend({
 		});
 	},
 
+	beforeRouteLeave(to, from, next) {
+		if (this.changed) {
+			this.$root.dialog({
+				type: 'warning',
+				text: this.$t('leave-confirm'),
+				showCancelButton: true
+			}).then(({ canceled }) => {
+				if (canceled) {
+					next(false);
+				} else {
+					next();
+				}
+			});
+		} else {
+			next();
+		}
+	},
+
 	beforeDestroy() {
 		room.destroy();
+		window.removeEventListener('beforeunload', this.beforeunload);
 	},
 
 	methods: {
+		beforeunload(e: BeforeUnloadEvent) {
+			if (this.changed) {
+				e.preventDefault();
+				e.returnValue = '';
+			}
+		},
+
 		async add() {
 			const { canceled, result: id } = await this.$root.dialog({
 				type: null,
@@ -143,17 +172,30 @@ export default Vue.extend({
 			});
 			if (canceled) return;
 			room.addFurniture(id);
+			this.changed = true;
 		},
 
 		remove() {
 			this.isTranslateMode = false;
 			this.isRotateMode = false;
 			room.removeFurniture();
+			this.changed = true;
 		},
 
 		save() {
 			this.$root.api('room/update', {
 				room: room.getRoomInfo()
+			}).then(() => {
+				this.changed = false;
+				this.$root.dialog({
+					type: 'success',
+					text: this.$t('saved')
+				});
+			}).catch((e: any) => {
+				this.$root.dialog({
+					type: 'error',
+					text: e.message
+				});
 			});
 		},
 
@@ -165,6 +207,7 @@ export default Vue.extend({
 			}).then(({ canceled }) => {
 				if (canceled) return;
 				room.removeAllFurnitures();
+				this.changed = true;
 			});
 		},
 
@@ -174,22 +217,26 @@ export default Vue.extend({
 			}).then(file => {
 				room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
 				this.$refs.preview.selected(room.getSelectedObject());
+				this.changed = true;
 			});
 		},
 
 		updateColor(key, ev) {
 			room.updateProp(key, ev.target.value);
 			this.$refs.preview.selected(room.getSelectedObject());
+			this.changed = true;
 		},
 
 		updateCarpetColor(ev) {
 			room.updateCarpetColor(ev.target.value);
 			this.carpetColor = ev.target.value;
+			this.changed = true;
 		},
 
 		updateRoomType(type) {
 			room.changeRoomType(type);
 			this.roomType = type;
+			this.changed = true;
 		},
 
 		translate() {
@@ -200,6 +247,7 @@ export default Vue.extend({
 				this.isTranslateMode = true;
 				room.enterTransformMode('translate');
 			}
+			this.changed = true;
 		},
 
 		rotate() {
@@ -210,12 +258,14 @@ export default Vue.extend({
 				this.isRotateMode = true;
 				room.enterTransformMode('rotate');
 			}
+			this.changed = true;
 		},
 
 		exit() {
 			this.isTranslateMode = false;
 			this.isRotateMode = false;
 			room.exitTransformMode();
+			this.changed = true;
 		}
 	}
 });