diff --git a/frontend/src/components/application-loader/initializers/index.ts b/frontend/src/components/application-loader/initializers/index.ts
index 6e4fe946a..be60c708c 100644
--- a/frontend/src/components/application-loader/initializers/index.ts
+++ b/frontend/src/components/application-loader/initializers/index.ts
@@ -8,7 +8,7 @@ import { Logger } from '../../../utils/logger'
 import { isDevMode, isTestMode } from '../../../utils/test-modes'
 import { loadDarkMode } from './load-dark-mode'
 import { setUpI18n } from './setupI18n'
-import { loadFromLocalStorage } from '../../../redux/editor/methods'
+import { loadFromLocalStorage } from '../../../redux/editor-config/methods'
 import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
 
 const logger = new Logger('Application Loader')
diff --git a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-add-form.spec.tsx b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-add-form.spec.tsx
index 37b755d54..29bdc7008 100644
--- a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-add-form.spec.tsx
+++ b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-add-form.spec.tsx
@@ -5,7 +5,7 @@
  */
 import * as AliasModule from '../../../../../../api/alias'
 import * as NoteDetailsReduxModule from '../../../../../../redux/note-details/methods'
-import type { NoteDetails } from '../../../../../../redux/note-details/types/note-details'
+import type { NoteDetails } from '../../../../../../redux/note-details/types'
 import { mockI18n } from '../../../../../../test-utils/mock-i18n'
 import { mockNotePermissions } from '../../../../../../test-utils/mock-note-permissions'
 import { AliasesAddForm } from './aliases-add-form'
diff --git a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-list.tsx b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-list.tsx
index aab779897..e2a845b06 100644
--- a/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-list.tsx
+++ b/frontend/src/components/editor-page/sidebar/specific-sidebar-entries/aliases-sidebar-entry/aliases-modal/aliases-list.tsx
@@ -5,7 +5,7 @@
  */
 import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
 import type { Alias } from '../../../../../../api/alias/types'
-import type { ApplicationState } from '../../../../../../redux/application-state'
+import type { ApplicationState } from '../../../../../../redux'
 import { AliasesListEntry } from './aliases-list-entry'
 import React, { Fragment, useMemo } from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/indent-spaces-setting-input.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/indent-spaces-setting-input.tsx
index 250365f7e..792bb96d0 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/indent-spaces-setting-input.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/indent-spaces-setting-input.tsx
@@ -6,7 +6,7 @@
 import React from 'react'
 import { Form } from 'react-bootstrap'
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorIndentSpaces } from '../../../../redux/editor/methods'
+import { setEditorIndentSpaces } from '../../../../redux/editor-config/methods'
 import { useCallback } from 'react'
 
 /**
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/indent-with-tabs-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/indent-with-tabs-setting-button-group.tsx
index 94770efca..9888aebfa 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/indent-with-tabs-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/indent-with-tabs-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorIndentWithTabs } from '../../../../redux/editor/methods'
+import { setEditorIndentWithTabs } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/ligature-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/ligature-setting-button-group.tsx
index 4a3efe55b..20846ab88 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/ligature-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/ligature-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorLigatures } from '../../../../redux/editor/methods'
+import { setEditorLigatures } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/line-wrapping-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/line-wrapping-setting-button-group.tsx
index 866476ca5..dbdd21704 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/line-wrapping-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/line-wrapping-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorLineWrapping } from '../../../../redux/editor/methods'
+import { setEditorLineWrapping } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/smart-paste-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/smart-paste-setting-button-group.tsx
index 1b6dd43bd..0533ae84f 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/smart-paste-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/smart-paste-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorSmartPaste } from '../../../../redux/editor/methods'
+import { setEditorSmartPaste } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/spellcheck-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/spellcheck-setting-button-group.tsx
index 6e6903f15..738342bc5 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/spellcheck-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/spellcheck-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorSpellCheck } from '../../../../redux/editor/methods'
+import { setEditorSpellCheck } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/components/global-dialogs/settings-dialog/editor/sync-scroll-setting-button-group.tsx b/frontend/src/components/global-dialogs/settings-dialog/editor/sync-scroll-setting-button-group.tsx
index ef50a1b90..aff350a0c 100644
--- a/frontend/src/components/global-dialogs/settings-dialog/editor/sync-scroll-setting-button-group.tsx
+++ b/frontend/src/components/global-dialogs/settings-dialog/editor/sync-scroll-setting-button-group.tsx
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { useApplicationState } from '../../../../hooks/common/use-application-state'
-import { setEditorSyncScroll } from '../../../../redux/editor/methods'
+import { setEditorSyncScroll } from '../../../../redux/editor-config/methods'
 import { OnOffButtonGroup } from '../utils/on-off-button-group'
 import React from 'react'
 
diff --git a/frontend/src/hooks/common/use-application-state.ts b/frontend/src/hooks/common/use-application-state.ts
index 65aaff8f8..5682c25c1 100644
--- a/frontend/src/hooks/common/use-application-state.ts
+++ b/frontend/src/hooks/common/use-application-state.ts
@@ -3,7 +3,7 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { ApplicationState } from '../../redux/application-state'
+import type { ApplicationState } from '../../redux'
 import equal from 'fast-deep-equal'
 import { useSelector } from 'react-redux'
 
diff --git a/frontend/src/redux/application-state.d.ts b/frontend/src/redux/application-state.d.ts
deleted file mode 100644
index 3b71843ec..000000000
--- a/frontend/src/redux/application-state.d.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { HistoryEntryWithOrigin } from '../api/history/types'
-import type { DarkModeConfig } from './dark-mode/types'
-import type { EditorConfig } from './editor/types'
-import type { RealtimeStatus } from './realtime/types'
-import type { RendererStatus } from './renderer-status/types'
-import type { OptionalUserState } from './user/types'
-import type { OptionalNoteDetails } from './note-details/types/note-details'
-
-export interface ApplicationState {
-  user: OptionalUserState
-  history: HistoryEntryWithOrigin[]
-  editorConfig: EditorConfig
-  darkMode: DarkModeConfig
-  noteDetails: OptionalNoteDetails
-  rendererStatus: RendererStatus
-  realtimeStatus: RealtimeStatus
-}
diff --git a/frontend/src/redux/dark-mode/initial-state.ts b/frontend/src/redux/dark-mode/initial-state.ts
new file mode 100644
index 000000000..259d13254
--- /dev/null
+++ b/frontend/src/redux/dark-mode/initial-state.ts
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { DarkModeConfig } from './types'
+import { DarkModePreference } from './types'
+
+export const initialState: DarkModeConfig = {
+  darkModePreference: DarkModePreference.AUTO
+}
diff --git a/frontend/src/redux/dark-mode/methods.ts b/frontend/src/redux/dark-mode/methods.ts
index 3abf2ed83..4ad5e4d01 100644
--- a/frontend/src/redux/dark-mode/methods.ts
+++ b/frontend/src/redux/dark-mode/methods.ts
@@ -4,12 +4,10 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { store } from '..'
-import type { DarkModeConfigAction, DarkModePreference } from './types'
-import { DarkModeConfigActionType } from './types'
+import type { DarkModePreference } from './types'
+import { darkModeActionsCreator } from './slice'
 
 export const setDarkModePreference = (darkModePreference: DarkModePreference): void => {
-  store.dispatch({
-    type: DarkModeConfigActionType.SET_DARK_MODE,
-    darkModePreference
-  } as DarkModeConfigAction)
+  const action = darkModeActionsCreator.setDarkModePreference(darkModePreference)
+  store.dispatch(action)
 }
diff --git a/frontend/src/redux/dark-mode/reducers.ts b/frontend/src/redux/dark-mode/reducers.ts
deleted file mode 100644
index 47afd8aed..000000000
--- a/frontend/src/redux/dark-mode/reducers.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { DarkModeConfig, DarkModeConfigAction } from './types'
-import { DarkModeConfigActionType, DarkModePreference } from './types'
-import type { Reducer } from 'redux'
-
-export const initialState: DarkModeConfig = {
-  darkModePreference: DarkModePreference.AUTO
-}
-
-export const DarkModeConfigReducer: Reducer<DarkModeConfig, DarkModeConfigAction> = (
-  state: DarkModeConfig = initialState,
-  action: DarkModeConfigAction
-) => {
-  switch (action.type) {
-    case DarkModeConfigActionType.SET_DARK_MODE:
-      return {
-        darkModePreference: action.darkModePreference
-      }
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/dark-mode/slice.ts b/frontend/src/redux/dark-mode/slice.ts
new file mode 100644
index 000000000..1c565aeb3
--- /dev/null
+++ b/frontend/src/redux/dark-mode/slice.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import type { DarkModeConfig } from './types'
+
+const darkModeSlice = createSlice({
+  name: 'darkMode',
+  initialState,
+  reducers: {
+    setDarkModePreference: (state, action: PayloadAction<DarkModeConfig['darkModePreference']>) => {
+      state.darkModePreference = action.payload
+    }
+  }
+})
+
+export const darkModeActionsCreator = darkModeSlice.actions
+export const darkModeReducer = darkModeSlice.reducer
diff --git a/frontend/src/redux/dark-mode/types.ts b/frontend/src/redux/dark-mode/types.ts
index 6825ca28f..5c09a8207 100644
--- a/frontend/src/redux/dark-mode/types.ts
+++ b/frontend/src/redux/dark-mode/types.ts
@@ -3,12 +3,6 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { Action } from 'redux'
-
-export enum DarkModeConfigActionType {
-  SET_DARK_MODE = 'dark-mode/set'
-}
-
 export enum DarkModePreference {
   DARK,
   LIGHT,
@@ -18,5 +12,3 @@ export enum DarkModePreference {
 export interface DarkModeConfig {
   darkModePreference: DarkModePreference
 }
-
-export type DarkModeConfigAction = Action<DarkModeConfigActionType.SET_DARK_MODE> & DarkModeConfig
diff --git a/frontend/src/redux/editor-config/initial-state.ts b/frontend/src/redux/editor-config/initial-state.ts
new file mode 100644
index 000000000..f8531ef66
--- /dev/null
+++ b/frontend/src/redux/editor-config/initial-state.ts
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { EditorConfig } from './types'
+
+export const initialState: EditorConfig = {
+  ligatures: true,
+  syncScroll: true,
+  smartPaste: true,
+  spellCheck: true,
+  lineWrapping: true,
+  indentWithTabs: false,
+  indentSpaces: 2
+}
diff --git a/frontend/src/redux/editor-config/methods.ts b/frontend/src/redux/editor-config/methods.ts
new file mode 100644
index 000000000..2fc89b51e
--- /dev/null
+++ b/frontend/src/redux/editor-config/methods.ts
@@ -0,0 +1,77 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { store } from '..'
+import { editorConfigActionsCreator } from './slice'
+import type { EditorConfig } from './types'
+import { initialState } from './initial-state'
+import { updateObject } from '../../utils/update-object'
+import { Logger } from '../../utils/logger'
+
+const log = new Logger('Redux > EditorConfig')
+
+export const setEditorSyncScroll = (syncScroll: boolean): void => {
+  const action = editorConfigActionsCreator.setSyncScroll(syncScroll)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorLineWrapping = (lineWrapping: boolean): void => {
+  const action = editorConfigActionsCreator.setLineWrapping(lineWrapping)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorLigatures = (ligatures: boolean): void => {
+  const action = editorConfigActionsCreator.setLigatures(ligatures)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorSmartPaste = (smartPaste: boolean): void => {
+  const action = editorConfigActionsCreator.setSmartPaste(smartPaste)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorSpellCheck = (spellCheck: boolean): void => {
+  const action = editorConfigActionsCreator.setSpellCheck(spellCheck)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorIndentWithTabs = (indentWithTabs: boolean): void => {
+  const action = editorConfigActionsCreator.setIndentWithTabs(indentWithTabs)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const setEditorIndentSpaces = (indentSpaces: number): void => {
+  const action = editorConfigActionsCreator.setIndentSpaces(indentSpaces)
+  store.dispatch(action)
+  saveToLocalStorage()
+}
+
+export const loadFromLocalStorage = (): void => {
+  try {
+    const config = { ...initialState }
+    const stored = window.localStorage.getItem('editorConfig')
+    const parsed = stored ? (JSON.parse(stored) as Partial<EditorConfig>) : null
+    updateObject(config, parsed)
+    const action = editorConfigActionsCreator.setEditorConfig(config)
+    store.dispatch(action)
+  } catch (error) {
+    log.error('Failed to load editor config from local storage', error)
+  }
+}
+
+const saveToLocalStorage = (): void => {
+  try {
+    const state = store.getState()
+    window.localStorage.setItem('editorConfig', JSON.stringify(state.editorConfig))
+  } catch (error) {
+    log.error('Failed to save editor config to local storage', error)
+  }
+}
diff --git a/frontend/src/redux/editor-config/slice.ts b/frontend/src/redux/editor-config/slice.ts
new file mode 100644
index 000000000..7cbf67c19
--- /dev/null
+++ b/frontend/src/redux/editor-config/slice.ts
@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import type { EditorConfig } from './types'
+
+const editorConfigSlice = createSlice({
+  name: 'editorConfig',
+  initialState,
+  reducers: {
+    setSyncScroll: (state, action: PayloadAction<EditorConfig['syncScroll']>) => {
+      state.syncScroll = action.payload
+    },
+    setLigatures: (state, action: PayloadAction<EditorConfig['ligatures']>) => {
+      state.ligatures = action.payload
+    },
+    setSmartPaste: (state, action: PayloadAction<EditorConfig['smartPaste']>) => {
+      state.smartPaste = action.payload
+    },
+    setSpellCheck: (state, action: PayloadAction<EditorConfig['spellCheck']>) => {
+      state.spellCheck = action.payload
+    },
+    setLineWrapping: (state, action: PayloadAction<EditorConfig['lineWrapping']>) => {
+      state.lineWrapping = action.payload
+    },
+    setIndentWithTabs: (state, action: PayloadAction<EditorConfig['indentWithTabs']>) => {
+      state.indentWithTabs = action.payload
+    },
+    setIndentSpaces: (state, action: PayloadAction<EditorConfig['indentSpaces']>) => {
+      state.indentSpaces = action.payload
+    },
+    setEditorConfig: (state, action: PayloadAction<EditorConfig>) => {
+      return action.payload
+    }
+  }
+})
+
+export const editorConfigActionsCreator = editorConfigSlice.actions
+export const editorConfigReducer = editorConfigSlice.reducer
diff --git a/frontend/src/redux/editor-config/types.ts b/frontend/src/redux/editor-config/types.ts
new file mode 100644
index 000000000..80ef77b2c
--- /dev/null
+++ b/frontend/src/redux/editor-config/types.ts
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+export interface EditorConfig {
+  syncScroll: boolean
+  ligatures: boolean
+  smartPaste: boolean
+  spellCheck: boolean
+  lineWrapping: boolean
+  indentWithTabs: boolean
+  indentSpaces: number
+}
diff --git a/frontend/src/redux/editor/methods.ts b/frontend/src/redux/editor/methods.ts
deleted file mode 100644
index 6ed0c8e8e..000000000
--- a/frontend/src/redux/editor/methods.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import { store } from '..'
-import type {
-  LoadFromLocalStorageAction,
-  SetEditorLigaturesAction,
-  SetEditorLineWrappingAction,
-  SetEditorSmartPasteAction,
-  SetEditorSyncScrollAction,
-  SetEditorSpellCheckAction,
-  SetEditorIndentWithTabsAction,
-  SetEditorIndentSpacesAction
-} from './types'
-import { EditorConfigActionType } from './types'
-
-export const setEditorSyncScroll = (syncScroll: boolean): void => {
-  const action: SetEditorSyncScrollAction = {
-    type: EditorConfigActionType.SET_SYNC_SCROLL,
-    syncScroll
-  }
-  store.dispatch(action)
-}
-
-export const setEditorLineWrapping = (lineWrapping: boolean): void => {
-  const action: SetEditorLineWrappingAction = {
-    type: EditorConfigActionType.SET_LINE_WRAPPING,
-    lineWrapping
-  }
-  store.dispatch(action)
-}
-
-export const setEditorLigatures = (ligatures: boolean): void => {
-  const action: SetEditorLigaturesAction = {
-    type: EditorConfigActionType.SET_LIGATURES,
-    ligatures
-  }
-  store.dispatch(action)
-}
-
-export const setEditorSmartPaste = (smartPaste: boolean): void => {
-  const action: SetEditorSmartPasteAction = {
-    type: EditorConfigActionType.SET_SMART_PASTE,
-    smartPaste
-  }
-  store.dispatch(action)
-}
-
-export const setEditorSpellCheck = (spellCheck: boolean): void => {
-  const action: SetEditorSpellCheckAction = {
-    type: EditorConfigActionType.SET_SPELL_CHECK,
-    spellCheck
-  }
-  store.dispatch(action)
-}
-
-export const setEditorIndentWithTabs = (indentWithTabs: boolean): void => {
-  const action: SetEditorIndentWithTabsAction = {
-    type: EditorConfigActionType.SET_INDENT_WITH_TABS,
-    indentWithTabs
-  }
-  store.dispatch(action)
-}
-
-export const setEditorIndentSpaces = (indentSpaces: number): void => {
-  const action: SetEditorIndentSpacesAction = {
-    type: EditorConfigActionType.SET_INDENT_SPACES,
-    indentSpaces
-  }
-  store.dispatch(action)
-}
-
-export const loadFromLocalStorage = (): void => {
-  const action: LoadFromLocalStorageAction = {
-    type: EditorConfigActionType.LOAD_FROM_LOCAL_STORAGE
-  }
-  store.dispatch(action)
-}
diff --git a/frontend/src/redux/editor/reducers.ts b/frontend/src/redux/editor/reducers.ts
deleted file mode 100644
index f24db2bf6..000000000
--- a/frontend/src/redux/editor/reducers.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { EditorConfig, EditorConfigActions } from './types'
-import { EditorConfigActionType } from './types'
-import type { Reducer } from 'redux'
-import { Logger } from '../../utils/logger'
-
-const logger = new Logger('EditorConfig Local Storage')
-
-export const initialState: EditorConfig = {
-  ligatures: true,
-  syncScroll: true,
-  smartPaste: true,
-  spellCheck: true,
-  lineWrapping: true,
-  indentWithTabs: false,
-  indentSpaces: 2
-}
-
-export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = (
-  state: EditorConfig = initialState,
-  action: EditorConfigActions
-) => {
-  let newState: EditorConfig
-  switch (action.type) {
-    case EditorConfigActionType.LOAD_FROM_LOCAL_STORAGE:
-      return loadFromLocalStorage() ?? initialState
-    case EditorConfigActionType.SET_SYNC_SCROLL:
-      newState = {
-        ...state,
-        syncScroll: action.syncScroll
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_LIGATURES:
-      newState = {
-        ...state,
-        ligatures: action.ligatures
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_SMART_PASTE:
-      newState = {
-        ...state,
-        smartPaste: action.smartPaste
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_SPELL_CHECK:
-      newState = {
-        ...state,
-        spellCheck: action.spellCheck
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_LINE_WRAPPING:
-      newState = {
-        ...state,
-        lineWrapping: action.lineWrapping
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_INDENT_WITH_TABS:
-      newState = {
-        ...state,
-        indentWithTabs: action.indentWithTabs
-      }
-      saveToLocalStorage(newState)
-      return newState
-    case EditorConfigActionType.SET_INDENT_SPACES:
-      newState = {
-        ...state,
-        indentSpaces: action.indentSpaces
-      }
-      saveToLocalStorage(newState)
-      return newState
-    default:
-      return state
-  }
-}
-
-export const loadFromLocalStorage = (): EditorConfig | undefined => {
-  try {
-    const stored = window.localStorage.getItem('editorConfig')
-    if (!stored) {
-      return undefined
-    }
-    const storedConfiguration = JSON.parse(stored) as Partial<EditorConfig>
-    return {
-      ligatures: storedConfiguration?.ligatures === true ?? true,
-      syncScroll: storedConfiguration?.syncScroll === true ?? true,
-      smartPaste: storedConfiguration?.smartPaste === true ?? true,
-      spellCheck: storedConfiguration?.spellCheck === true ?? true,
-      lineWrapping: storedConfiguration?.lineWrapping === true ?? true,
-      indentWithTabs: storedConfiguration?.indentWithTabs === true ?? false,
-      indentSpaces: storedConfiguration?.indentSpaces ?? 2
-    }
-  } catch (_) {
-    return undefined
-  }
-}
-
-export const saveToLocalStorage = (editorConfig: EditorConfig): void => {
-  try {
-    const json = JSON.stringify(editorConfig)
-    localStorage.setItem('editorConfig', json)
-  } catch (error) {
-    logger.error('Error while saving editor config in local storage', error)
-  }
-}
diff --git a/frontend/src/redux/editor/types.ts b/frontend/src/redux/editor/types.ts
deleted file mode 100644
index e88d194ed..000000000
--- a/frontend/src/redux/editor/types.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { Action } from 'redux'
-
-export enum EditorConfigActionType {
-  SET_SYNC_SCROLL = 'editor/syncScroll/set',
-  LOAD_FROM_LOCAL_STORAGE = 'editor/preferences/load',
-  SET_LIGATURES = 'editor/preferences/setLigatures',
-  SET_LINE_WRAPPING = 'editor/preferences/setLineWrapping',
-  SET_SMART_PASTE = 'editor/preferences/setSmartPaste',
-  SET_SPELL_CHECK = 'editor/preferences/setSpellCheck',
-  SET_INDENT_WITH_TABS = 'editor/preferences/setIndentWithTabs',
-  SET_INDENT_SPACES = 'editor/preferences/setIndentSpaces'
-}
-
-export interface EditorConfig {
-  syncScroll: boolean
-  ligatures: boolean
-  smartPaste: boolean
-  spellCheck: boolean
-  lineWrapping: boolean
-  indentWithTabs: boolean
-  indentSpaces: number
-}
-
-export type EditorConfigActions =
-  | SetEditorSyncScrollAction
-  | SetEditorLigaturesAction
-  | SetEditorSmartPasteAction
-  | SetEditorLineWrappingAction
-  | SetEditorSpellCheckAction
-  | SetEditorIndentWithTabsAction
-  | SetEditorIndentSpacesAction
-  | LoadFromLocalStorageAction
-
-export interface LoadFromLocalStorageAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.LOAD_FROM_LOCAL_STORAGE
-}
-
-export interface SetEditorLineWrappingAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_LINE_WRAPPING
-  lineWrapping: boolean
-}
-
-export interface SetEditorSyncScrollAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_SYNC_SCROLL
-  syncScroll: boolean
-}
-
-export interface SetEditorLigaturesAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_LIGATURES
-  ligatures: boolean
-}
-
-export interface SetEditorSmartPasteAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_SMART_PASTE
-  smartPaste: boolean
-}
-
-export interface SetEditorSpellCheckAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_SPELL_CHECK
-  spellCheck: boolean
-}
-
-export interface SetEditorIndentWithTabsAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_INDENT_WITH_TABS
-  indentWithTabs: boolean
-}
-
-export interface SetEditorIndentSpacesAction extends Action<EditorConfigActionType> {
-  type: EditorConfigActionType.SET_INDENT_SPACES
-  indentSpaces: number
-}
diff --git a/frontend/src/redux/history/initial-state.ts b/frontend/src/redux/history/initial-state.ts
new file mode 100644
index 000000000..33f78b3ec
--- /dev/null
+++ b/frontend/src/redux/history/initial-state.ts
@@ -0,0 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { HistoryState } from './types'
+
+export const initialState: HistoryState = []
diff --git a/frontend/src/redux/history/methods.ts b/frontend/src/redux/history/methods.ts
index d1bf5282d..013b4fa73 100644
--- a/frontend/src/redux/history/methods.ts
+++ b/frontend/src/redux/history/methods.ts
@@ -15,10 +15,10 @@ import type { HistoryEntry, HistoryEntryWithOrigin } from '../../api/history/typ
 import { HistoryEntryOrigin } from '../../api/history/types'
 import { download } from '../../components/common/download/download'
 import { Logger } from '../../utils/logger'
-import { getGlobalState, store } from '../index'
-import type { HistoryExportJson, RemoveEntryAction, SetEntriesAction, UpdateEntryAction, V1HistoryEntry } from './types'
-import { HistoryActionType } from './types'
+import { store } from '../index'
+import type { HistoryExportJson, V1HistoryEntry } from './types'
 import { DateTime } from 'luxon'
+import { historyActionsCreator } from './slice'
 
 const log = new Logger('Redux > History')
 
@@ -27,10 +27,8 @@ const log = new Logger('Redux > History')
  * @param entries The history entries to set into the redux state.
  */
 export const setHistoryEntries = (entries: HistoryEntryWithOrigin[]): void => {
-  store.dispatch({
-    type: HistoryActionType.SET_ENTRIES,
-    entries
-  } as SetEntriesAction)
+  const action = historyActionsCreator.setEntries(entries)
+  store.dispatch(action)
   storeLocalHistory()
 }
 
@@ -47,10 +45,8 @@ export const importHistoryEntries = (entries: HistoryEntryWithOrigin[]): Promise
  * Deletes all history entries in the redux, local-storage and on the server.
  */
 export const deleteAllHistoryEntries = (): Promise<unknown> => {
-  store.dispatch({
-    type: HistoryActionType.SET_ENTRIES,
-    entries: []
-  } as SetEntriesAction)
+  const action = historyActionsCreator.setEntries([])
+  store.dispatch(action)
   storeLocalHistory()
   return deleteRemoteHistory()
 }
@@ -61,11 +57,11 @@ export const deleteAllHistoryEntries = (): Promise<unknown> => {
  * @param newEntry The modified history entry.
  */
 export const updateHistoryEntryRedux = (noteId: string, newEntry: HistoryEntry): void => {
-  store.dispatch({
-    type: HistoryActionType.UPDATE_ENTRY,
+  const action = historyActionsCreator.updateEntry({
     noteId,
     newEntry
-  } as UpdateEntryAction)
+  })
+  store.dispatch(action)
 }
 
 /**
@@ -83,14 +79,12 @@ export const updateLocalHistoryEntry = (noteId: string, newEntry: HistoryEntry):
  * @param noteId The note id of the history entry to delete.
  */
 export const removeHistoryEntry = async (noteId: string): Promise<void> => {
-  const entryToDelete = getGlobalState().history.find((entry) => entry.identifier === noteId)
+  const entryToDelete = store.getState().history.find((entry) => entry.identifier === noteId)
   if (entryToDelete && entryToDelete.origin === HistoryEntryOrigin.REMOTE) {
     await deleteRemoteHistoryEntry(noteId)
   }
-  store.dispatch({
-    type: HistoryActionType.REMOVE_ENTRY,
-    noteId
-  } as RemoveEntryAction)
+  const action = historyActionsCreator.removeEntry({ noteId })
+  store.dispatch(action)
   storeLocalHistory()
 }
 
@@ -99,7 +93,7 @@ export const removeHistoryEntry = async (noteId: string): Promise<void> => {
  * @param noteId The note id of the history entry to update.
  */
 export const toggleHistoryEntryPinning = async (noteId: string): Promise<void> => {
-  const state = getGlobalState().history
+  const state = store.getState().history
   const entryToUpdate = state.find((entry) => entry.identifier === noteId)
   if (!entryToUpdate) {
     return Promise.reject(`History entry for note '${noteId}' not found`)
@@ -120,7 +114,7 @@ export const toggleHistoryEntryPinning = async (noteId: string): Promise<void> =
  * Exports the current history redux state into a JSON file that will be downloaded by the client.
  */
 export const downloadHistory = (): void => {
-  const history = getGlobalState().history
+  const history = store.getState().history
   history.forEach((entry: Partial<HistoryEntryWithOrigin>) => {
     delete entry.origin
   })
@@ -166,7 +160,7 @@ export const convertV1History = (oldHistory: V1HistoryEntry[]): HistoryEntryWith
  */
 export const refreshHistoryState = async (): Promise<void> => {
   const localEntries = loadLocalHistory()
-  if (!getGlobalState().user) {
+  if (!store.getState().user) {
     setHistoryEntries(localEntries)
     return
   }
@@ -179,7 +173,7 @@ export const refreshHistoryState = async (): Promise<void> => {
  * Stores the history entries marked as local from the redux to the user's local-storage.
  */
 export const storeLocalHistory = (): void => {
-  const history = getGlobalState().history
+  const history = store.getState().history
   const localEntries = history.filter((entry) => entry.origin === HistoryEntryOrigin.LOCAL)
   const entriesWithoutOrigin = localEntries.map((entry) => ({
     ...entry,
@@ -196,10 +190,10 @@ export const storeLocalHistory = (): void => {
  * Stores the history entries marked as remote from the redux to the server.
  */
 export const storeRemoteHistory = (): Promise<unknown> => {
-  if (!getGlobalState().user) {
+  if (!store.getState().user) {
     return Promise.resolve()
   }
-  const history = getGlobalState().history
+  const history = store.getState().history
   const remoteEntries = history.filter((entry) => entry.origin === HistoryEntryOrigin.REMOTE)
   const remoteEntryDtos = remoteEntries.map(historyEntryToHistoryEntryPutDto)
   return setRemoteHistoryEntries(remoteEntryDtos)
diff --git a/frontend/src/redux/history/reducers.ts b/frontend/src/redux/history/reducers.ts
deleted file mode 100644
index 4a13b764d..000000000
--- a/frontend/src/redux/history/reducers.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { HistoryEntryWithOrigin } from '../../api/history/types'
-import type { HistoryActions } from './types'
-import { HistoryActionType } from './types'
-import type { Reducer } from 'redux'
-
-// Q: Why is the reducer initialized with an empty array instead of the actual history entries like in the config reducer?
-// A: The history reducer will be created without entries because of async entry retrieval.
-//    Entries will be added after reducer initialization.
-
-export const HistoryReducer: Reducer<HistoryEntryWithOrigin[], HistoryActions> = (
-  state: HistoryEntryWithOrigin[] = [],
-  action: HistoryActions
-) => {
-  switch (action.type) {
-    case HistoryActionType.SET_ENTRIES:
-      return action.entries
-    case HistoryActionType.UPDATE_ENTRY:
-      return [...state.filter((entry) => entry.identifier !== action.noteId), action.newEntry]
-    case HistoryActionType.REMOVE_ENTRY:
-      return state.filter((entry) => entry.identifier !== action.noteId)
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/history/slice.ts b/frontend/src/redux/history/slice.ts
new file mode 100644
index 000000000..b96c2d4d8
--- /dev/null
+++ b/frontend/src/redux/history/slice.ts
@@ -0,0 +1,34 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import type { HistoryState, RemoveEntryPayload, UpdateEntryPayload } from './types'
+import type { HistoryEntryWithOrigin } from '../../api/history/types'
+
+const historySlice = createSlice({
+  name: 'history',
+  initialState,
+  reducers: {
+    setEntries: (state, action: PayloadAction<HistoryState>) => {
+      return action.payload
+    },
+    updateEntry: (state, action: PayloadAction<UpdateEntryPayload>) => {
+      const entryToUpdateIndex = state.findIndex((entry) => entry.identifier === action.payload.noteId)
+      if (entryToUpdateIndex < 0) {
+        return state
+      }
+      const updatedEntry: HistoryEntryWithOrigin = { ...state[entryToUpdateIndex], ...action.payload.newEntry }
+      return state.toSpliced(entryToUpdateIndex, 1, updatedEntry)
+    },
+    removeEntry: (state, action: PayloadAction<RemoveEntryPayload>) => {
+      return state.filter((entry) => entry.identifier !== action.payload.noteId)
+    }
+  }
+})
+
+export const historyActionsCreator = historySlice.actions
+export const historyReducer = historySlice.reducer
diff --git a/frontend/src/redux/history/types.ts b/frontend/src/redux/history/types.ts
index b70ae193b..bada8afb0 100644
--- a/frontend/src/redux/history/types.ts
+++ b/frontend/src/redux/history/types.ts
@@ -3,8 +3,9 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { HistoryEntryWithOrigin } from '../../api/history/types'
-import type { Action } from 'redux'
+import type { HistoryEntry, HistoryEntryWithOrigin } from '../../api/history/types'
+
+export type HistoryState = HistoryEntryWithOrigin[]
 
 export interface V1HistoryEntry {
   id: string
@@ -19,32 +20,11 @@ export interface HistoryExportJson {
   entries: HistoryEntryWithOrigin[]
 }
 
-export enum HistoryActionType {
-  SET_ENTRIES = 'SET_ENTRIES',
-  ADD_ENTRY = 'ADD_ENTRY',
-  UPDATE_ENTRY = 'UPDATE_ENTRY',
-  REMOVE_ENTRY = 'REMOVE_ENTRY'
-}
-
-export type HistoryActions = SetEntriesAction | AddEntryAction | UpdateEntryAction | RemoveEntryAction
-
-export interface SetEntriesAction extends Action<HistoryActionType> {
-  type: HistoryActionType.SET_ENTRIES
-  entries: HistoryEntryWithOrigin[]
-}
-
-export interface AddEntryAction extends Action<HistoryActionType> {
-  type: HistoryActionType.ADD_ENTRY
-  newEntry: HistoryEntryWithOrigin
-}
-
-export interface UpdateEntryAction extends Action<HistoryActionType> {
-  type: HistoryActionType.UPDATE_ENTRY
+export interface UpdateEntryPayload {
   noteId: string
-  newEntry: HistoryEntryWithOrigin
+  newEntry: HistoryEntry
 }
 
-export interface RemoveEntryAction extends Action<HistoryActionType> {
-  type: HistoryActionType.REMOVE_ENTRY
+export interface RemoveEntryPayload {
   noteId: string
 }
diff --git a/frontend/src/redux/index.ts b/frontend/src/redux/index.ts
index 34632841e..ad9bed194 100644
--- a/frontend/src/redux/index.ts
+++ b/frontend/src/redux/index.ts
@@ -4,13 +4,28 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { isDevMode } from '../utils/test-modes'
-import type { ApplicationState } from './application-state'
-import { allReducers } from './reducers'
 import { configureStore } from '@reduxjs/toolkit'
+import { darkModeReducer } from './dark-mode/slice'
+import { editorConfigReducer } from './editor-config/slice'
+import { userReducer } from './user/slice'
+import { rendererStatusReducer } from './renderer-status/slice'
+import { realtimeStatusReducer } from './realtime/slice'
+import { historyReducer } from './history/slice'
+import { noteDetailsReducer } from './note-details/slice'
 
 export const store = configureStore({
-  reducer: allReducers,
+  reducer: {
+    darkMode: darkModeReducer,
+    editorConfig: editorConfigReducer,
+    user: userReducer,
+    rendererStatus: rendererStatusReducer,
+    realtimeStatus: realtimeStatusReducer,
+    history: historyReducer,
+    noteDetails: noteDetailsReducer
+  },
   devTools: isDevMode
 })
 
+export type ApplicationState = ReturnType<typeof store.getState>
+
 export const getGlobalState = (): ApplicationState => store.getState()
diff --git a/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts b/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts
index 1ce7acb0a..56fb0acde 100644
--- a/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts
+++ b/frontend/src/redux/note-details/build-state-from-updated-markdown-content.ts
@@ -5,7 +5,7 @@
  */
 import { calculateLineStartIndexes } from './calculate-line-start-indexes'
 import { initialState } from './initial-state'
-import type { NoteDetails } from './types/note-details'
+import type { NoteDetails } from './types'
 import type { FrontmatterExtractionResult, NoteFrontmatter } from '@hedgedoc/commons'
 import {
   convertRawFrontmatterToNoteFrontmatter,
diff --git a/frontend/src/redux/note-details/initial-state.ts b/frontend/src/redux/note-details/initial-state.ts
index e65bb3c4c..e4aa751fc 100644
--- a/frontend/src/redux/note-details/initial-state.ts
+++ b/frontend/src/redux/note-details/initial-state.ts
@@ -3,7 +3,7 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { NoteDetails } from './types/note-details'
+import type { NoteDetails } from './types'
 import { defaultNoteFrontmatter } from '@hedgedoc/commons'
 
 export const initialState: NoteDetails = {
diff --git a/frontend/src/redux/note-details/methods.ts b/frontend/src/redux/note-details/methods.ts
index 52113656a..0a3bde0c3 100644
--- a/frontend/src/redux/note-details/methods.ts
+++ b/frontend/src/redux/note-details/methods.ts
@@ -7,26 +7,16 @@ import { store } from '..'
 import { getNoteMetadata } from '../../api/notes'
 import type { Note } from '../../api/notes/types'
 import type { CursorSelection } from '../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
-import type {
-  SetNoteDetailsFromServerAction,
-  SetNoteDocumentContentAction,
-  SetNotePermissionsFromServerAction,
-  UpdateCursorPositionAction,
-  UpdateMetadataAction,
-  UpdateNoteTitleByFirstHeadingAction
-} from './types'
-import { NoteDetailsActionType } from './types'
 import type { NotePermissions } from '@hedgedoc/commons'
+import { noteDetailsActionsCreator } from './slice'
 
 /**
  * Sets the content of the current note, extracts and parses the frontmatter and extracts the markdown content part.
  * @param content The note content as it is written inside the editor pane.
  */
 export const setNoteContent = (content: string): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.SET_DOCUMENT_CONTENT,
-    content: content
-  } as SetNoteDocumentContentAction)
+  const action = noteDetailsActionsCreator.setNoteContent(content)
+  store.dispatch(action)
 }
 
 /**
@@ -34,10 +24,8 @@ export const setNoteContent = (content: string): void => {
  * @param apiResponse The NoteDTO received from the API to store into redux.
  */
 export const setNoteDataFromServer = (apiResponse: Note): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER,
-    noteFromServer: apiResponse
-  } as SetNoteDetailsFromServerAction)
+  const action = noteDetailsActionsCreator.setNoteDataFromServer(apiResponse)
+  store.dispatch(action)
 }
 
 /**
@@ -45,10 +33,8 @@ export const setNoteDataFromServer = (apiResponse: Note): void => {
  * @param apiResponse The NotePermissionsDTO received from the API to store into redux.
  */
 export const setNotePermissionsFromServer = (apiResponse: NotePermissions): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER,
-    notePermissionsFromServer: apiResponse
-  } as SetNotePermissionsFromServerAction)
+  const action = noteDetailsActionsCreator.setNotePermissionsFromServer(apiResponse)
+  store.dispatch(action)
 }
 
 /**
@@ -56,17 +42,13 @@ export const setNotePermissionsFromServer = (apiResponse: NotePermissions): void
  * @param firstHeading The content of the first heading found in the markdown content.
  */
 export const updateNoteTitleByFirstHeading = (firstHeading?: string): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.UPDATE_NOTE_TITLE_BY_FIRST_HEADING,
-    firstHeading: firstHeading
-  } as UpdateNoteTitleByFirstHeadingAction)
+  const action = noteDetailsActionsCreator.updateNoteTitleByFirstHeading(firstHeading)
+  store.dispatch(action)
 }
 
 export const updateCursorPositions = (selection: CursorSelection): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.UPDATE_CURSOR_POSITION,
-    selection
-  } as UpdateCursorPositionAction)
+  const action = noteDetailsActionsCreator.updateCursorPosition(selection)
+  store.dispatch(action)
 }
 
 /**
@@ -78,14 +60,11 @@ export const updateMetadata = async (): Promise<void> => {
     return
   }
   const updatedMetadata = await getNoteMetadata(noteDetails.id)
-  store.dispatch({
-    type: NoteDetailsActionType.UPDATE_METADATA,
-    updatedMetadata
-  } as UpdateMetadataAction)
+  const action = noteDetailsActionsCreator.updateMetadata(updatedMetadata)
+  store.dispatch(action)
 }
 
 export const unloadNote = (): void => {
-  store.dispatch({
-    type: NoteDetailsActionType.UNLOAD_NOTE
-  })
+  const action = noteDetailsActionsCreator.unloadNote()
+  store.dispatch(action)
 }
diff --git a/frontend/src/redux/note-details/reducer.ts b/frontend/src/redux/note-details/reducer.ts
deleted file mode 100644
index 3463e808a..000000000
--- a/frontend/src/redux/note-details/reducer.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import { buildStateFromUpdatedMarkdownContent } from './build-state-from-updated-markdown-content'
-import { buildStateFromFirstHeadingUpdate } from './reducers/build-state-from-first-heading-update'
-import { buildStateFromMetadataUpdate } from './reducers/build-state-from-metadata-update'
-import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions'
-import { buildStateFromServerDto } from './reducers/build-state-from-set-note-data-from-server'
-import { buildStateFromUpdateCursorPosition } from './reducers/build-state-from-update-cursor-position'
-import type { NoteDetailsActions } from './types'
-import { NoteDetailsActionType } from './types'
-import type { OptionalNoteDetails } from './types/note-details'
-import type { Reducer } from 'redux'
-
-export const NoteDetailsReducer: Reducer<OptionalNoteDetails, NoteDetailsActions> = (
-  state: OptionalNoteDetails = null,
-  action: NoteDetailsActions
-) => {
-  if (action.type === NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER) {
-    return buildStateFromServerDto(action.noteFromServer)
-  }
-  if (state === null) {
-    return null
-  }
-  switch (action.type) {
-    case NoteDetailsActionType.UPDATE_CURSOR_POSITION:
-      return buildStateFromUpdateCursorPosition(state, action.selection)
-    case NoteDetailsActionType.SET_DOCUMENT_CONTENT:
-      return buildStateFromUpdatedMarkdownContent(state, action.content)
-    case NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER:
-      return buildStateFromServerPermissions(state, action.notePermissionsFromServer)
-    case NoteDetailsActionType.UPDATE_NOTE_TITLE_BY_FIRST_HEADING:
-      return buildStateFromFirstHeadingUpdate(state, action.firstHeading)
-    case NoteDetailsActionType.UPDATE_METADATA:
-      return buildStateFromMetadataUpdate(state, action.updatedMetadata)
-    case NoteDetailsActionType.UNLOAD_NOTE:
-      return null
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-first-heading-update.ts b/frontend/src/redux/note-details/reducers/build-state-from-first-heading-update.ts
index 95aa97ab2..b7b17311e 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-first-heading-update.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-first-heading-update.ts
@@ -3,7 +3,7 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { generateNoteTitle } from '@hedgedoc/commons'
 
 /**
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.spec.ts b/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.spec.ts
index 98fea9e5d..f92f74424 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.spec.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.spec.ts
@@ -5,7 +5,7 @@
  */
 import type { NoteMetadata } from '../../../api/notes/types'
 import { initialState } from '../initial-state'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { buildStateFromMetadataUpdate } from './build-state-from-metadata-update'
 
 describe('build state from server permissions', () => {
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.ts b/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.ts
index 6378e258d..a571e0759 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-metadata-update.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import type { NoteMetadata } from '../../../api/notes/types'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { DateTime } from 'luxon'
 
 /**
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.spec.ts b/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.spec.ts
index 88c4ec7e8..fc510ae59 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.spec.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.spec.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { initialState } from '../initial-state'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { buildStateFromServerPermissions } from './build-state-from-server-permissions'
 import type { NotePermissions } from '@hedgedoc/commons'
 
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.ts b/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.ts
index 7281f0f2f..bbf98895b 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-server-permissions.ts
@@ -3,7 +3,7 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import type { NotePermissions } from '@hedgedoc/commons'
 
 /**
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts
index 70ff50c4b..847802ceb 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.spec.ts
@@ -5,7 +5,7 @@
  */
 import type { Note } from '../../../api/notes/types'
 import * as buildStateFromUpdatedMarkdownContentModule from '../build-state-from-updated-markdown-content'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { buildStateFromServerDto } from './build-state-from-set-note-data-from-server'
 import { NoteTextDirection, NoteType } from '@hedgedoc/commons'
 import { DateTime } from 'luxon'
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.ts b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.ts
index 620ff9de3..543637d09 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-set-note-data-from-server.ts
@@ -7,7 +7,7 @@ import type { Note } from '../../../api/notes/types'
 import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
 import { calculateLineStartIndexes } from '../calculate-line-start-indexes'
 import { initialState } from '../initial-state'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { buildStateFromMetadataUpdate } from './build-state-from-metadata-update'
 
 /**
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.spec.ts b/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.spec.ts
index d07ec1db3..88311c020 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.spec.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.spec.ts
@@ -5,7 +5,7 @@
  */
 import * as buildStateFromUpdatedMarkdownContentLinesModule from '../build-state-from-updated-markdown-content'
 import { initialState } from '../initial-state'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { buildStateFromTaskListUpdate } from './build-state-from-task-list-update'
 import { Mock } from 'ts-mockery'
 
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.ts b/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.ts
index 02467dd86..1968a46ac 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-task-list-update.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { buildStateFromUpdatedMarkdownContentLines } from '../build-state-from-updated-markdown-content'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 import { Optional } from '@mrdrogdrog/optional'
 
 const TASK_REGEX = /(\s*(?:[-*+]|\d+[.)]) )\[[ xX]?]( .*)/
diff --git a/frontend/src/redux/note-details/reducers/build-state-from-update-cursor-position.ts b/frontend/src/redux/note-details/reducers/build-state-from-update-cursor-position.ts
index ec7aad164..7ae460668 100644
--- a/frontend/src/redux/note-details/reducers/build-state-from-update-cursor-position.ts
+++ b/frontend/src/redux/note-details/reducers/build-state-from-update-cursor-position.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import type { CursorSelection } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
-import type { NoteDetails } from '../types/note-details'
+import type { NoteDetails } from '../types'
 
 export const buildStateFromUpdateCursorPosition = (state: NoteDetails, selection: CursorSelection): NoteDetails => {
   const correctedSelection = isFromAfterTo(selection)
diff --git a/frontend/src/redux/note-details/slice.ts b/frontend/src/redux/note-details/slice.ts
new file mode 100644
index 000000000..3d9857f04
--- /dev/null
+++ b/frontend/src/redux/note-details/slice.ts
@@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import { buildStateFromServerDto } from './reducers/build-state-from-set-note-data-from-server'
+import type { Note, NoteMetadata } from '../../api/notes/types'
+import { buildStateFromUpdatedMarkdownContent } from './build-state-from-updated-markdown-content'
+import type { NotePermissions } from '@hedgedoc/commons/dist/esm'
+import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions'
+import { buildStateFromFirstHeadingUpdate } from './reducers/build-state-from-first-heading-update'
+import { buildStateFromMetadataUpdate } from './reducers/build-state-from-metadata-update'
+import type { CursorSelection } from '../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
+import { buildStateFromUpdateCursorPosition } from './reducers/build-state-from-update-cursor-position'
+
+const noteDetailsSlice = createSlice({
+  name: 'noteDetails',
+  initialState,
+  reducers: {
+    setNoteDataFromServer(_, action: PayloadAction<Note>) {
+      return buildStateFromServerDto(action.payload)
+    },
+    setNoteContent(state, action: PayloadAction<string>) {
+      return buildStateFromUpdatedMarkdownContent(state, action.payload)
+    },
+    setNotePermissionsFromServer(state, action: PayloadAction<NotePermissions>) {
+      return buildStateFromServerPermissions(state, action.payload)
+    },
+    updateNoteTitleByFirstHeading(state, action: PayloadAction<string | undefined>) {
+      return buildStateFromFirstHeadingUpdate(state, action.payload)
+    },
+    updateMetadata(state, action: PayloadAction<NoteMetadata>) {
+      return buildStateFromMetadataUpdate(state, action.payload)
+    },
+    updateCursorPosition(state, action: PayloadAction<CursorSelection>) {
+      return buildStateFromUpdateCursorPosition(state, action.payload)
+    },
+    unloadNote() {
+      return initialState
+    }
+  }
+})
+
+export const noteDetailsActionsCreator = noteDetailsSlice.actions
+export const noteDetailsReducer = noteDetailsSlice.reducer
diff --git a/frontend/src/redux/note-details/types.ts b/frontend/src/redux/note-details/types.ts
index e92b4f756..08f8f0f9d 100644
--- a/frontend/src/redux/note-details/types.ts
+++ b/frontend/src/redux/note-details/types.ts
@@ -3,75 +3,26 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { Note, NoteMetadata } from '../../api/notes/types'
+import type { NoteMetadata } from '../../api/notes/types'
 import type { CursorSelection } from '../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
-import type { NotePermissions } from '@hedgedoc/commons'
-import type { Action } from 'redux'
+import type { NoteFrontmatter } from '@hedgedoc/commons'
 
-export enum NoteDetailsActionType {
-  SET_DOCUMENT_CONTENT = 'note-details/content/set',
-  SET_NOTE_DATA_FROM_SERVER = 'note-details/data/server/set',
-  SET_NOTE_PERMISSIONS_FROM_SERVER = 'note-details/data/permissions/set',
-  UPDATE_NOTE_TITLE_BY_FIRST_HEADING = 'note-details/update-note-title-by-first-heading',
-  UPDATE_CURSOR_POSITION = 'note-details/updateCursorPosition',
-  UPDATE_METADATA = 'note-details/update-metadata',
-  UNLOAD_NOTE = 'note-details/unload-note'
-}
-
-export type NoteDetailsActions =
-  | SetNoteDocumentContentAction
-  | SetNoteDetailsFromServerAction
-  | SetNotePermissionsFromServerAction
-  | UpdateNoteTitleByFirstHeadingAction
-  | UpdateCursorPositionAction
-  | UpdateMetadataAction
-  | UnloadNoteAction
+type UnnecessaryNoteAttributes = 'updatedAt' | 'createdAt' | 'tags' | 'description'
 
 /**
- * Action for updating the document content of the currently loaded note.
+ * Redux state containing the currently loaded note with its content and metadata.
  */
-export interface SetNoteDocumentContentAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.SET_DOCUMENT_CONTENT
-  content: string
-}
-
-/**
- * Action for overwriting the current state with the data received from the API.
- */
-export interface SetNoteDetailsFromServerAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER
-  noteFromServer: Note
-}
-
-/**
- * Action for overwriting the current permission state with the data received from the API.
- */
-export interface SetNotePermissionsFromServerAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER
-  notePermissionsFromServer: NotePermissions
-}
-
-/**
- * Action for updating the note title of the currently loaded note by using frontmatter data or the first heading.
- */
-export interface UpdateNoteTitleByFirstHeadingAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.UPDATE_NOTE_TITLE_BY_FIRST_HEADING
-  firstHeading?: string
-}
-
-export interface UpdateCursorPositionAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.UPDATE_CURSOR_POSITION
+export interface NoteDetails extends Omit<NoteMetadata, UnnecessaryNoteAttributes> {
+  updatedAt: number
+  createdAt: number
+  markdownContent: {
+    plain: string
+    lines: string[]
+    lineStartIndexes: number[]
+  }
   selection: CursorSelection
-}
-
-/**
- * Action for updating the metadata of the current note.
- */
-export interface UpdateMetadataAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.UPDATE_METADATA
-  updatedMetadata: NoteMetadata
-}
-
-export interface UnloadNoteAction extends Action<NoteDetailsActionType> {
-  type: NoteDetailsActionType.UNLOAD_NOTE
+  firstHeading?: string
+  rawFrontmatter: string
+  frontmatter: NoteFrontmatter
+  startOfContentLineOffset: number
 }
diff --git a/frontend/src/redux/note-details/types/note-details.ts b/frontend/src/redux/note-details/types/note-details.ts
deleted file mode 100644
index 9a4f19cad..000000000
--- a/frontend/src/redux/note-details/types/note-details.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { NoteMetadata } from '../../../api/notes/types'
-import type { CursorSelection } from '../../../components/editor-page/editor-pane/tool-bar/formatters/types/cursor-selection'
-import type { NoteFrontmatter } from '@hedgedoc/commons'
-
-type UnnecessaryNoteAttributes = 'updatedAt' | 'createdAt' | 'tags' | 'description'
-
-/**
- * Redux state containing the currently loaded note with its content and metadata.
- */
-export interface NoteDetails extends Omit<NoteMetadata, UnnecessaryNoteAttributes> {
-  updatedAt: number
-  createdAt: number
-  markdownContent: {
-    plain: string
-    lines: string[]
-    lineStartIndexes: number[]
-  }
-  selection: CursorSelection
-  firstHeading?: string
-  rawFrontmatter: string
-  frontmatter: NoteFrontmatter
-  startOfContentLineOffset: number
-}
-
-export type OptionalNoteDetails = NoteDetails | null
diff --git a/frontend/src/redux/realtime/initial-state.ts b/frontend/src/redux/realtime/initial-state.ts
new file mode 100644
index 000000000..c0f38ac8f
--- /dev/null
+++ b/frontend/src/redux/realtime/initial-state.ts
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { RealtimeStatus } from './types'
+
+export const initialState: RealtimeStatus = {
+  isSynced: false,
+  isConnected: false,
+  onlineUsers: [],
+  ownUser: {
+    displayName: '',
+    styleIndex: 0
+  }
+}
diff --git a/frontend/src/redux/realtime/methods.ts b/frontend/src/redux/realtime/methods.ts
index c35110153..91312b742 100644
--- a/frontend/src/redux/realtime/methods.ts
+++ b/frontend/src/redux/realtime/methods.ts
@@ -4,41 +4,34 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { store } from '..'
-import type { SetRealtimeConnectionStatusAction, SetRealtimeSyncStatusAction, SetRealtimeUsersAction } from './types'
-import { RealtimeStatusActionType } from './types'
 import type { RealtimeUser } from '@hedgedoc/commons'
+import { realtimeStatusActionsCreator } from './slice'
 
 /**
  * Dispatches an event to add a user
  */
 export const setRealtimeUsers = (users: RealtimeUser[], ownStyleIndex: number, ownDisplayName: string): void => {
-  const action: SetRealtimeUsersAction = {
-    type: RealtimeStatusActionType.SET_REALTIME_USERS,
+  const action = realtimeStatusActionsCreator.setRealtimeUsers({
     users,
     ownUser: {
       styleIndex: ownStyleIndex,
       displayName: ownDisplayName
     }
-  }
+  })
   store.dispatch(action)
 }
 
 export const setRealtimeConnectionState = (status: boolean): void => {
-  store.dispatch({
-    type: RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS,
-    isConnected: status
-  } as SetRealtimeConnectionStatusAction)
+  const action = realtimeStatusActionsCreator.setRealtimeConnectionStatus(status)
+  store.dispatch(action)
 }
 
 export const setRealtimeSyncedState = (status: boolean): void => {
-  store.dispatch({
-    type: RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS,
-    isSynced: status
-  } as SetRealtimeSyncStatusAction)
+  const action = realtimeStatusActionsCreator.setRealtimeSyncStatus(status)
+  store.dispatch(action)
 }
 
 export const resetRealtimeStatus = (): void => {
-  store.dispatch({
-    type: RealtimeStatusActionType.RESET_REALTIME_STATUS
-  })
+  const action = realtimeStatusActionsCreator.resetRealtimeStatus()
+  store.dispatch(action)
 }
diff --git a/frontend/src/redux/realtime/reducers.ts b/frontend/src/redux/realtime/reducers.ts
deleted file mode 100644
index 90eedc7e1..000000000
--- a/frontend/src/redux/realtime/reducers.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { RealtimeStatus, RealtimeStatusActions } from './types'
-import { RealtimeStatusActionType } from './types'
-import type { Reducer } from 'redux'
-
-export const initialState: RealtimeStatus = {
-  isSynced: false,
-  isConnected: false,
-  onlineUsers: [],
-  ownUser: {
-    displayName: '',
-    styleIndex: 0
-  }
-}
-
-/**
- * Applies {@link RealtimeStatusReducer realtime actions} to the global application state.
- *
- * @param state the current state
- * @param action the action that should get applied
- * @return The new changed state
- */
-export const RealtimeStatusReducer: Reducer<RealtimeStatus, RealtimeStatusActions> = (
-  state = initialState,
-  action: RealtimeStatusActions
-) => {
-  switch (action.type) {
-    case RealtimeStatusActionType.SET_REALTIME_USERS:
-      return {
-        ...state,
-        onlineUsers: action.users,
-        ownUser: action.ownUser
-      }
-    case RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS:
-      return {
-        ...state,
-        isConnected: action.isConnected
-      }
-    case RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS:
-      return {
-        ...state,
-        isSynced: action.isSynced
-      }
-    case RealtimeStatusActionType.RESET_REALTIME_STATUS:
-      return initialState
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/realtime/slice.ts b/frontend/src/redux/realtime/slice.ts
new file mode 100644
index 000000000..3ba2cad00
--- /dev/null
+++ b/frontend/src/redux/realtime/slice.ts
@@ -0,0 +1,32 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import type { RealtimeStatus, SetRealtimeUsersPayload } from './types'
+
+const realtimeStatusSlice = createSlice({
+  name: 'realtimeStatus',
+  initialState,
+  reducers: {
+    setRealtimeUsers(state, action: PayloadAction<SetRealtimeUsersPayload>) {
+      state.onlineUsers = action.payload.users
+      state.ownUser = action.payload.ownUser
+    },
+    setRealtimeConnectionStatus(state, action: PayloadAction<RealtimeStatus['isConnected']>) {
+      state.isConnected = action.payload
+    },
+    setRealtimeSyncStatus(state, action: PayloadAction<RealtimeStatus['isSynced']>) {
+      state.isSynced = action.payload
+    },
+    resetRealtimeStatus() {
+      return initialState
+    }
+  }
+})
+
+export const realtimeStatusActionsCreator = realtimeStatusSlice.actions
+export const realtimeStatusReducer = realtimeStatusSlice.reducer
diff --git a/frontend/src/redux/realtime/types.ts b/frontend/src/redux/realtime/types.ts
index 931decebf..987a951e0 100644
--- a/frontend/src/redux/realtime/types.ts
+++ b/frontend/src/redux/realtime/types.ts
@@ -4,17 +4,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import type { RealtimeUser } from '@hedgedoc/commons'
-import type { Action } from 'redux'
 
-export enum RealtimeStatusActionType {
-  SET_REALTIME_USERS = 'realtime/set-users',
-  SET_REALTIME_CONNECTION_STATUS = 'realtime/set-connection-status',
-  SET_REALTIME_SYNCED_STATUS = 'realtime/set-synced-status',
-  RESET_REALTIME_STATUS = 'realtime/reset-realtime-status'
-}
-
-export interface SetRealtimeUsersAction extends Action<RealtimeStatusActionType> {
-  type: RealtimeStatusActionType.SET_REALTIME_USERS
+export interface SetRealtimeUsersPayload {
   users: RealtimeUser[]
   ownUser: {
     styleIndex: number
@@ -22,20 +13,6 @@ export interface SetRealtimeUsersAction extends Action<RealtimeStatusActionType>
   }
 }
 
-export interface SetRealtimeConnectionStatusAction extends Action<RealtimeStatusActionType> {
-  type: RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS
-  isConnected: boolean
-}
-
-export interface SetRealtimeSyncStatusAction extends Action<RealtimeStatusActionType> {
-  type: RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS
-  isSynced: boolean
-}
-
-export interface ResetRealtimeStatusAction extends Action<RealtimeStatusActionType> {
-  type: RealtimeStatusActionType.RESET_REALTIME_STATUS
-}
-
 export interface RealtimeStatus {
   onlineUsers: RealtimeUser[]
   isConnected: boolean
@@ -45,9 +22,3 @@ export interface RealtimeStatus {
     styleIndex: number
   }
 }
-
-export type RealtimeStatusActions =
-  | SetRealtimeUsersAction
-  | SetRealtimeConnectionStatusAction
-  | SetRealtimeSyncStatusAction
-  | ResetRealtimeStatusAction
diff --git a/frontend/src/redux/reducers.ts b/frontend/src/redux/reducers.ts
deleted file mode 100644
index 38a00e5c0..000000000
--- a/frontend/src/redux/reducers.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { ApplicationState } from './application-state'
-import { DarkModeConfigReducer } from './dark-mode/reducers'
-import { EditorConfigReducer } from './editor/reducers'
-import { HistoryReducer } from './history/reducers'
-import { NoteDetailsReducer } from './note-details/reducer'
-import { RealtimeStatusReducer } from './realtime/reducers'
-import { RendererStatusReducer } from './renderer-status/reducers'
-import { UserReducer } from './user/reducers'
-import type { Reducer } from 'redux'
-import { combineReducers } from 'redux'
-
-export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
-  user: UserReducer,
-  history: HistoryReducer,
-  editorConfig: EditorConfigReducer,
-  darkMode: DarkModeConfigReducer,
-  noteDetails: NoteDetailsReducer,
-  rendererStatus: RendererStatusReducer,
-  realtimeStatus: RealtimeStatusReducer
-})
diff --git a/frontend/src/redux/renderer-status/initial-state.ts b/frontend/src/redux/renderer-status/initial-state.ts
new file mode 100644
index 000000000..3db6fcfdb
--- /dev/null
+++ b/frontend/src/redux/renderer-status/initial-state.ts
@@ -0,0 +1,10 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { RendererStatus } from './types'
+
+export const initialState: RendererStatus = {
+  rendererReady: false
+}
diff --git a/frontend/src/redux/renderer-status/methods.ts b/frontend/src/redux/renderer-status/methods.ts
index a287631cd..b7b1472f7 100644
--- a/frontend/src/redux/renderer-status/methods.ts
+++ b/frontend/src/redux/renderer-status/methods.ts
@@ -4,8 +4,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import { store } from '..'
-import type { SetRendererStatusAction } from './types'
-import { RendererStatusActionType } from './types'
+import { rendererStatusActionsCreator } from './slice'
 
 /**
  * Dispatches a global application state change for the "renderer ready" state.
@@ -13,9 +12,6 @@ import { RendererStatusActionType } from './types'
  * @param rendererReady The new renderer ready state.
  */
 export const setRendererStatus = (rendererReady: boolean): void => {
-  const action: SetRendererStatusAction = {
-    type: RendererStatusActionType.SET_RENDERER_STATUS,
-    rendererReady
-  }
+  const action = rendererStatusActionsCreator.setRendererStatus(rendererReady)
   store.dispatch(action)
 }
diff --git a/frontend/src/redux/renderer-status/reducers.ts b/frontend/src/redux/renderer-status/reducers.ts
deleted file mode 100644
index 8086d8746..000000000
--- a/frontend/src/redux/renderer-status/reducers.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { RendererStatus, RendererStatusActions } from './types'
-import { RendererStatusActionType } from './types'
-import type { Reducer } from 'redux'
-
-export const initialState: RendererStatus = {
-  rendererReady: false
-}
-
-/**
- * Applies {@link RendererStatusActions renderer status actions} to the global application state.
- *
- * @param state the current state
- * @param action the action that should get applied
- * @return The new changed state
- */
-export const RendererStatusReducer: Reducer<RendererStatus, RendererStatusActions> = (
-  state: RendererStatus = initialState,
-  action: RendererStatusActions
-) => {
-  switch (action.type) {
-    case RendererStatusActionType.SET_RENDERER_STATUS:
-      return {
-        ...state,
-        rendererReady: action.rendererReady
-      }
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/renderer-status/slice.ts b/frontend/src/redux/renderer-status/slice.ts
new file mode 100644
index 000000000..9ef2b98c5
--- /dev/null
+++ b/frontend/src/redux/renderer-status/slice.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { initialState } from './initial-state'
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import type { RendererStatus } from './types'
+
+const rendererStatusSlice = createSlice({
+  name: 'rendererStatus',
+  initialState,
+  reducers: {
+    setRendererStatus: (state, action: PayloadAction<RendererStatus['rendererReady']>) => {
+      state.rendererReady = action.payload
+    }
+  }
+})
+
+export const rendererStatusActionsCreator = rendererStatusSlice.actions
+export const rendererStatusReducer = rendererStatusSlice.reducer
diff --git a/frontend/src/redux/renderer-status/types.ts b/frontend/src/redux/renderer-status/types.ts
index a3b5825b5..842a07f1d 100644
--- a/frontend/src/redux/renderer-status/types.ts
+++ b/frontend/src/redux/renderer-status/types.ts
@@ -3,19 +3,6 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { Action } from 'redux'
-
-export enum RendererStatusActionType {
-  SET_RENDERER_STATUS = 'renderer-status/set-ready'
-}
-
 export interface RendererStatus {
   rendererReady: boolean
 }
-
-export interface SetRendererStatusAction extends Action<RendererStatusActionType> {
-  type: RendererStatusActionType.SET_RENDERER_STATUS
-  rendererReady: boolean
-}
-
-export type RendererStatusActions = SetRendererStatusAction
diff --git a/frontend/src/redux/user/initial-state.ts b/frontend/src/redux/user/initial-state.ts
new file mode 100644
index 000000000..c61e3ff7d
--- /dev/null
+++ b/frontend/src/redux/user/initial-state.ts
@@ -0,0 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { UserState } from './types'
+
+export const initialState: UserState = null
diff --git a/frontend/src/redux/user/methods.ts b/frontend/src/redux/user/methods.ts
index 5ff29cefc..d8521ee8b 100644
--- a/frontend/src/redux/user/methods.ts
+++ b/frontend/src/redux/user/methods.ts
@@ -5,27 +5,21 @@
  */
 import { store } from '..'
 import type { LoginUserInfo } from '../../api/me/types'
-import type { ClearUserAction, SetUserAction } from './types'
-import { UserActionType } from './types'
+import { userActionsCreator } from './slice'
 
 /**
  * Sets the given user state into the redux.
  * @param state The user state to set into the redux.
  */
 export const setUser = (state: LoginUserInfo): void => {
-  const action: SetUserAction = {
-    type: UserActionType.SET_USER,
-    state
-  }
+  const action = userActionsCreator.setUser(state)
   store.dispatch(action)
 }
 
 /**
  * Clears the user state from the redux.
  */
-export const clearUser: () => void = () => {
-  const action: ClearUserAction = {
-    type: UserActionType.CLEAR_USER
-  }
+export const clearUser = (): void => {
+  const action = userActionsCreator.setUser(null)
   store.dispatch(action)
 }
diff --git a/frontend/src/redux/user/reducers.ts b/frontend/src/redux/user/reducers.ts
deleted file mode 100644
index 27e91b89d..000000000
--- a/frontend/src/redux/user/reducers.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-import type { OptionalUserState, UserActions } from './types'
-import { UserActionType } from './types'
-import type { Reducer } from 'redux'
-
-export const UserReducer: Reducer<OptionalUserState, UserActions> = (
-  state: OptionalUserState = null,
-  action: UserActions
-) => {
-  switch (action.type) {
-    case UserActionType.SET_USER:
-      return action.state
-    case UserActionType.CLEAR_USER:
-      return null
-    default:
-      return state
-  }
-}
diff --git a/frontend/src/redux/user/slice.ts b/frontend/src/redux/user/slice.ts
new file mode 100644
index 000000000..73ade59d3
--- /dev/null
+++ b/frontend/src/redux/user/slice.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { PayloadAction } from '@reduxjs/toolkit'
+import { createSlice } from '@reduxjs/toolkit'
+import { initialState } from './initial-state'
+import type { UserState } from './types'
+
+const userSlice = createSlice({
+  name: 'user',
+  initialState,
+  reducers: {
+    setUser: (state, action: PayloadAction<UserState>) => {
+      return action.payload
+    }
+  }
+})
+
+export const userActionsCreator = userSlice.actions
+export const userReducer = userSlice.reducer
diff --git a/frontend/src/redux/user/types.ts b/frontend/src/redux/user/types.ts
index 129f17a3b..011e8d6f1 100644
--- a/frontend/src/redux/user/types.ts
+++ b/frontend/src/redux/user/types.ts
@@ -4,22 +4,5 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import type { LoginUserInfo } from '../../api/me/types'
-import type { Action } from 'redux'
 
-export enum UserActionType {
-  SET_USER = 'user/set',
-  CLEAR_USER = 'user/clear'
-}
-
-export type UserActions = SetUserAction | ClearUserAction
-
-export interface SetUserAction extends Action<UserActionType> {
-  type: UserActionType.SET_USER
-  state: LoginUserInfo
-}
-
-export interface ClearUserAction extends Action<UserActionType> {
-  type: UserActionType.CLEAR_USER
-}
-
-export type OptionalUserState = LoginUserInfo | null
+export type UserState = LoginUserInfo | null
diff --git a/frontend/src/test-utils/mock-app-state.ts b/frontend/src/test-utils/mock-app-state.ts
index 729c7afb8..70fb1a1ce 100644
--- a/frontend/src/test-utils/mock-app-state.ts
+++ b/frontend/src/test-utils/mock-app-state.ts
@@ -4,17 +4,17 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 import * as useApplicationStateModule from '../hooks/common/use-application-state'
-import type { ApplicationState } from '../redux/application-state'
-import { initialState as initialStateDarkMode } from '../redux/dark-mode/reducers'
-import { initialState as initialStateEditorConfig } from '../redux/editor/reducers'
+import type { ApplicationState } from '../redux'
+import { initialState as initialStateDarkMode } from '../redux/dark-mode/initial-state'
+import { initialState as initialStateEditorConfig } from '../redux/editor-config/initial-state'
 import { initialState as initialStateNoteDetails } from '../redux/note-details/initial-state'
-import { initialState as initialStateRealtimeStatus } from '../redux/realtime/reducers'
-import { initialState as initialStateRendererStatus } from '../redux/renderer-status/reducers'
-import type { NoteDetails } from '../redux/note-details/types/note-details'
+import { initialState as initialStateRealtimeStatus } from '../redux/realtime/initial-state'
+import { initialState as initialStateRendererStatus } from '../redux/renderer-status/initial-state'
+import type { NoteDetails } from '../redux/note-details/types'
 import type { RealtimeStatus } from '../redux/realtime/types'
 import type { DeepPartial } from '@hedgedoc/commons'
 
-jest.mock('../redux/editor/methods', () => ({
+jest.mock('../redux/editor-config/methods', () => ({
   loadFromLocalStorage: jest.fn().mockReturnValue(undefined)
 }))
 jest.mock('../hooks/common/use-application-state')
diff --git a/frontend/src/test-utils/mock-note-permissions.ts b/frontend/src/test-utils/mock-note-permissions.ts
index 4ca09453b..0aa8758a3 100644
--- a/frontend/src/test-utils/mock-note-permissions.ts
+++ b/frontend/src/test-utils/mock-note-permissions.ts
@@ -3,7 +3,7 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { ApplicationState } from '../redux/application-state'
+import type { ApplicationState } from '../redux'
 import { mockAppState } from './mock-app-state'
 import type { DeepPartial, NotePermissions } from '@hedgedoc/commons'
 
diff --git a/frontend/src/utils/update-object.ts b/frontend/src/utils/update-object.ts
new file mode 100644
index 000000000..5ea80f435
--- /dev/null
+++ b/frontend/src/utils/update-object.ts
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+export const updateObject = <T extends Record<string, unknown>>(oldObject: T, newValues: T | null): void => {
+  if (typeof newValues !== 'object' || newValues === null) {
+    return
+  }
+  Object.keys(oldObject).forEach((key) => {
+    if (Object.prototype.hasOwnProperty.call(newValues, key) && typeof oldObject[key] === typeof newValues[key]) {
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-expect-error
+      oldObject[key] = newValues[key]
+    }
+  })
+}