diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c82711790b0f50f395d609cb8238f77388623cc7
--- /dev/null
+++ b/app/javascript/mastodon/components/router.tsx
@@ -0,0 +1,23 @@
+import type { PropsWithChildren } from 'react';
+import React from 'react';
+
+import type { History } from 'history';
+import { createBrowserHistory } from 'history';
+import { Router as OriginalRouter } from 'react-router';
+
+import { layoutFromWindow } from 'mastodon/is_mobile';
+
+const browserHistory = createBrowserHistory();
+const originalPush = browserHistory.push.bind(browserHistory);
+
+browserHistory.push = (path: string, state: History.LocationState) => {
+  if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
+    originalPush(`/deck${path}`, state);
+  } else {
+    originalPush(path, state);
+  }
+};
+
+export const Router: React.FC<PropsWithChildren> = ({ children }) => {
+  return <OriginalRouter history={browserHistory}>{children}</OriginalRouter>;
+};
diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx
index 4538db050d1cbc64eb8052ac0e86bd2a3145c756..59efc80570dc6cf468e2326ef35bdc95654263cf 100644
--- a/app/javascript/mastodon/containers/mastodon.jsx
+++ b/app/javascript/mastodon/containers/mastodon.jsx
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
 import { Helmet } from 'react-helmet';
-import { BrowserRouter, Route } from 'react-router-dom';
+import { Route } from 'react-router-dom';
 
 import { Provider as ReduxProvider } from 'react-redux';
 
@@ -12,6 +12,7 @@ import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
 import { hydrateStore } from 'mastodon/actions/store';
 import { connectUserStream } from 'mastodon/actions/streaming';
 import ErrorBoundary from 'mastodon/components/error_boundary';
+import { Router } from 'mastodon/components/router';
 import UI from 'mastodon/features/ui';
 import initialState, { title as siteTitle } from 'mastodon/initial_state';
 import { IntlProvider } from 'mastodon/locales';
@@ -75,11 +76,11 @@ export default class Mastodon extends PureComponent {
       <IntlProvider>
         <ReduxProvider store={store}>
           <ErrorBoundary>
-            <BrowserRouter>
+            <Router>
               <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
                 <Route path='/' component={UI} />
               </ScrollContext>
-            </BrowserRouter>
+            </Router>
 
             <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
           </ErrorBoundary>
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index dc406fa55cce59358ee83ebfa24833e493581690..ab5c78246f9eaaacfca84df6143f8b23738db30f 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
 import { WordmarkLogo } from 'mastodon/components/logo';
 import NavigationPortal from 'mastodon/components/navigation_portal';
 import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
+import { transientSingleColumn } from 'mastodon/is_mobile';
 
 import ColumnLink from './column_link';
 import DisabledAccountBanner from './disabled_account_banner';
@@ -29,6 +30,7 @@ const messages = defineMessages({
   followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
   about: { id: 'navigation_bar.about', defaultMessage: 'About' },
   search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
+  advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
 });
 
 class NavigationPanel extends Component {
@@ -54,6 +56,12 @@ class NavigationPanel extends Component {
       <div className='navigation-panel'>
         <div className='navigation-panel__logo'>
           <Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
+
+          {transientSingleColumn && (
+            <a href={`/deck${location.pathname}`} className='button button--block'>
+              {intl.formatMessage(messages.advancedInterface)}
+            </a>
+          )}
           <hr />
         </div>
 
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx
index b38acfc14dd0fca5350523bbd153984811c0cbb5..ae81a354b2146d7147621b2aad6b7b2ee61fa973 100644
--- a/app/javascript/mastodon/features/ui/index.jsx
+++ b/app/javascript/mastodon/features/ui/index.jsx
@@ -126,11 +126,11 @@ class SwitchingColumnsArea extends PureComponent {
   static propTypes = {
     children: PropTypes.node,
     location: PropTypes.object,
-    mobile: PropTypes.bool,
+    singleColumn: PropTypes.bool,
   };
 
   UNSAFE_componentWillMount () {
-    if (this.props.mobile) {
+    if (this.props.singleColumn) {
       document.body.classList.toggle('layout-single-column', true);
       document.body.classList.toggle('layout-multiple-columns', false);
     } else {
@@ -144,9 +144,9 @@ class SwitchingColumnsArea extends PureComponent {
       this.node.handleChildrenContentChange();
     }
 
-    if (prevProps.mobile !== this.props.mobile) {
-      document.body.classList.toggle('layout-single-column', this.props.mobile);
-      document.body.classList.toggle('layout-multiple-columns', !this.props.mobile);
+    if (prevProps.singleColumn !== this.props.singleColumn) {
+      document.body.classList.toggle('layout-single-column', this.props.singleColumn);
+      document.body.classList.toggle('layout-multiple-columns', !this.props.singleColumn);
     }
   }
 
@@ -157,16 +157,17 @@ class SwitchingColumnsArea extends PureComponent {
   };
 
   render () {
-    const { children, mobile } = this.props;
+    const { children, singleColumn } = this.props;
     const { signedIn } = this.context.identity;
+    const pathName = this.props.location.pathname;
 
     let redirect;
 
     if (signedIn) {
-      if (mobile) {
+      if (singleColumn) {
         redirect = <Redirect from='/' to='/home' exact />;
       } else {
-        redirect = <Redirect from='/' to='/getting-started' exact />;
+        redirect = <Redirect from='/' to='/deck/getting-started' exact />;
       }
     } else if (singleUserMode && owner && initialState?.accounts[owner]) {
       redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
@@ -177,10 +178,13 @@ class SwitchingColumnsArea extends PureComponent {
     }
 
     return (
-      <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
+      <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}>
         <WrappedSwitch>
           {redirect}
 
+          {singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
+          {singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
+
           <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
           <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
           <WrappedRoute path='/about' component={About} content={children} />
@@ -573,7 +577,7 @@ class UI extends PureComponent {
         <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
           <Header />
 
-          <SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}>
+          <SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
             {children}
           </SwitchingColumnsArea>
 
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
index 66cfee970814b1f183e43e0115e02e7cf0535ba3..99277268573cc584d71ca75356847ed91ea59b77 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
@@ -11,13 +11,21 @@ import BundleContainer from '../containers/bundle_container';
 
 // Small wrapper to pass multiColumn to the route components
 export class WrappedSwitch extends PureComponent {
+  static contextTypes = {
+    router: PropTypes.object,
+  };
 
   render () {
     const { multiColumn, children } = this.props;
+    const { location } = this.context.router.route;
+
+    const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
+      ? {...location, pathname: location.pathname.slice(5)}
+      : location;
 
     return (
-      <Switch>
-        {Children.map(children, child => cloneElement(child, { multiColumn }))}
+      <Switch location={decklessLocation}>
+        {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
       </Switch>
     );
   }
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 5ad61e1f6b87cf576492006c61825ed6b3f59ae1..67fb068432c7ce11189dc9ebd1c08a9003b59cf0 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -94,6 +94,13 @@ const element = document.getElementById('initial-state');
 /** @type {InitialState | undefined} */
 const initialState = element?.textContent && JSON.parse(element.textContent);
 
+/** @type {string} */
+const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? '';
+/** @type {boolean} */
+export const hasMultiColumnPath = initialPath === '/'
+  || initialPath === '/getting-started'
+  || initialPath.startsWith('/deck');
+
 /**
  * @template {keyof InitialStateMeta} K
  * @param {K} prop
diff --git a/app/javascript/mastodon/is_mobile.ts b/app/javascript/mastodon/is_mobile.ts
index 36cde21332ea1faab6daa4e96b23cf761f721d28..7f339e287bfd6165b8f2eb03042b5a43de395b5f 100644
--- a/app/javascript/mastodon/is_mobile.ts
+++ b/app/javascript/mastodon/is_mobile.ts
@@ -1,19 +1,21 @@
 import { supportsPassiveEvents } from 'detect-passive-events';
 
-import { forceSingleColumn } from './initial_state';
+import { forceSingleColumn, hasMultiColumnPath } from './initial_state';
 
 const LAYOUT_BREAKPOINT = 630;
 
 export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT;
 
+export const transientSingleColumn = !forceSingleColumn && !hasMultiColumnPath;
+
 export type LayoutType = 'mobile' | 'single-column' | 'multi-column';
 export const layoutFromWindow = (): LayoutType => {
   if (isMobile(window.innerWidth)) {
     return 'mobile';
-  } else if (forceSingleColumn) {
-    return 'single-column';
-  } else {
+  } else if (!forceSingleColumn && !transientSingleColumn) {
     return 'multi-column';
+  } else {
+    return 'single-column';
   }
 };
 
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index edecaf60f395d304a245ec5ecf2515ad4132728b..8c85cb7bea9c550cdfefc2e5c223d3023ac11f04 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -385,6 +385,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "mute_modal.indefinite": "Indefinite",
   "navigation_bar.about": "About",
+  "navigation_bar.advanced_interface": "Open in advanced web interface",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.bookmarks": "Bookmarks",
   "navigation_bar.community_timeline": "Local timeline",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 75b7890d27af5642252463cdfea8c8ab3bc558fd..13eb9762efcb7593d986b98f3236e8ce76c688e6 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -368,6 +368,7 @@
   "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
   "mute_modal.indefinite": "Indéfinie",
   "navigation_bar.about": "À propos",
+  "navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée",
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.bookmarks": "Marque-pages",
   "navigation_bar.community_timeline": "Fil public local",
diff --git a/app/views/shared/_web_app.html.haml b/app/views/shared/_web_app.html.haml
index 998cee9fa9e1679905a284c9568db2209aa51cc8..9a1c3dc0bf432c8973a8e4820af3181abde669d6 100644
--- a/app/views/shared/_web_app.html.haml
+++ b/app/views/shared/_web_app.html.haml
@@ -3,6 +3,7 @@
     = preload_pack_asset 'features/compose.js', crossorigin: 'anonymous'
     = preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous'
     = preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous'
+    %meta{ name: 'initialPath', content: request.path }
 
   %meta{ name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key }
 
diff --git a/config/routes.rb b/config/routes.rb
index fa72d8b06546e873f8f4cf9a828632bdee1eb70d..87ee815f415704f81a53b8f15064ef4d3e74f0d5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,6 +30,7 @@ Rails.application.routes.draw do
     /mutes
     /followed_tags
     /statuses/(*any)
+    /deck/(*any)
   ).freeze
 
   root 'home#index'
diff --git a/package.json b/package.json
index 4f99f25f1e3eaac3c6f60ff6ab8c552369479617..b46dada7d023081a1880cc71dbdc7948bf590ebe 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,7 @@
     "react-overlays": "^5.2.1",
     "react-redux": "^8.0.4",
     "react-redux-loading-bar": "^5.0.4",
+    "react-router": "^4.3.1",
     "react-router-dom": "^4.1.1",
     "react-router-scroll-4": "^1.0.0-beta.1",
     "react-select": "^5.7.3",