diff --git a/src/components/common/ServerHeader.tsx b/src/components/common/ServerHeader.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e2a89df811d231061a88ad7758987369de2a2b64
--- /dev/null
+++ b/src/components/common/ServerHeader.tsx
@@ -0,0 +1,39 @@
+import Header from "../ui/Header";
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import IconButton from "../ui/IconButton";
+import { Settings } from "@styled-icons/feather";
+import { Server } from "revolt.js/dist/api/objects";
+import { ServerPermission } from "revolt.js/dist/api/permissions";
+import { HookContext, useServerPermission } from "../../context/revoltjs/hooks";
+
+interface Props {
+    server: Server,
+    ctx: HookContext
+}
+
+const ServerName = styled.div`
+    flex-grow: 1;
+`;
+
+export default function ServerHeader({ server, ctx }: Props) {
+    const permissions = useServerPermission(server._id, ctx);
+    const bannerURL = ctx.client.servers.getBannerURL(server._id, { width: 480 }, true);
+
+    return (
+        <Header placement="secondary"
+            background={typeof bannerURL !== 'undefined'}
+            style={{ background: bannerURL ? `linear-gradient(to bottom, transparent 50%, #000e), url('${bannerURL}')` : undefined }}>
+            <ServerName>
+                { server.name }
+            </ServerName>
+            { (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
+                <Link to={`/server/${server._id}/settings`}>
+                    <IconButton>
+                        <Settings size={24} />
+                    </IconButton>
+                </Link>
+            </div> }
+        </Header>
+    )
+}
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index f4cae5fdd538bd417361c486b106ad7043b357ae..b84bb69c4f7a7b90f7f59c8ba11858fd4da44fdd 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -14,6 +14,7 @@ import { connectState } from "../../../redux/connector";
 import PaintCounter from "../../../lib/PaintCounter";
 import styled from "styled-components";
 import { attachContextMenu } from 'preact-context-menu';
+import ServerHeader from "../../common/ServerHeader";
 
 interface Props {
     unreads: Unreads;
@@ -45,7 +46,6 @@ function ServerSidebar(props: Props & WithDispatcher) {
     const server = useServer(server_id, ctx);
     if (!server) return <Redirect to="/" />;
 
-    const permissions = useServerPermission(server._id, ctx);
     const channels = (useChannels(server.channels, ctx)
         .filter(entry => typeof entry !== 'undefined') as Readonly<Channels.TextChannel>[])
         .map(x => mapChannelWithUnread(x, props.unreads));
@@ -55,16 +55,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
 
     return (
         <ServerBase>
-            <Header placement="secondary" background style={{ background: `url('${ctx.client.servers.getBannerURL(server._id, { width: 480 }, true)}')` }}>
-                <div>
-                    { server.name }
-                </div>
-                { (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
-                    {/*<IconButton to={`/server/${server._id}/settings`}>*/}
-                        <Settings size={24} />
-                    {/*</IconButton>*/}
-                </div> }
-            </Header>
+            <ServerHeader server={server} ctx={ctx} />
             <ConnectionStatus />
             <ServerList onContextMenu={attachContextMenu('Menu', { server_list: server._id })}>
                 {channels.map(entry => {
diff --git a/src/pages/Open.tsx b/src/pages/Open.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7ee8120cfee956098ff60e9606faf0f4c9216f03
--- /dev/null
+++ b/src/pages/Open.tsx
@@ -0,0 +1,72 @@
+import { Text } from "preact-i18n";
+import Header from "../components/ui/Header";
+import { useContext, useEffect } from "preact/hooks";
+import { useHistory, useParams } from "react-router-dom";
+import { useIntermediate } from "../context/intermediate/Intermediate";
+import { useChannels, useForceUpdate, useUser } from "../context/revoltjs/hooks";
+import { AppContext, ClientStatus, StatusContext } from "../context/revoltjs/RevoltClient";
+
+export default function Open() {
+    const history = useHistory();
+    const client = useContext(AppContext);
+    const status = useContext(StatusContext);
+    const { id } = useParams<{ id: string }>();
+    const { openScreen } = useIntermediate();
+
+    if (status !== ClientStatus.ONLINE) {
+        return (
+            <Header placement="primary">
+                <Text id="general.loading" />
+            </Header>
+        );
+    }
+
+    const ctx = useForceUpdate();
+    const channels = useChannels(undefined, ctx);
+    const user = useUser(id, ctx);
+
+    useEffect(() => {
+        if (id === "saved") {
+            for (const channel of channels) {
+                if (channel?.channel_type === "SavedMessages") {
+                    history.push(`/channel/${channel._id}`);
+                    return;
+                }
+            }
+
+            client.users
+                .openDM(client.user?._id as string)
+                .then(channel => history.push(`/channel/${channel?._id}`))
+                .catch(error => openScreen({ id: "error", error }));
+
+            return;
+        }
+
+        if (user) {
+            const channel: string | undefined = channels.find(
+                channel =>
+                    channel?.channel_type === "DirectMessage" &&
+                    channel.recipients.includes(id)
+            )?._id;
+
+            if (channel) {
+                history.push(`/channel/${channel}`);
+            } else {
+                client.users
+                    .openDM(id)
+                    .then(channel => history.push(`/channel/${channel?._id}`))
+                    .catch(error => openScreen({ id: "error", error }));
+            }
+
+            return;
+        }
+
+        history.push("/");
+    }, []);
+
+    return (
+        <Header placement="primary">
+            <Text id="general.loading" />
+        </Header>
+    );
+}
diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx
index f20c20317ea1fb5b0fefd4901f0cb735a1cdb2ae..53400ce80482a278e8fc260cd32f18eb6693a070 100644
--- a/src/pages/RevoltApp.tsx
+++ b/src/pages/RevoltApp.tsx
@@ -13,7 +13,9 @@ import LeftSidebar from "../components/navigation/LeftSidebar";
 import RightSidebar from "../components/navigation/RightSidebar";
 import BottomNavigation from "../components/navigation/BottomNavigation";
 
+import Open from "./Open";
 import Home from './home/Home';
+import Invite from "./invite/Invite";
 import Friends from "./friends/Friends";
 import Channel from "./channels/Channel";
 import Settings from './settings/Settings';
@@ -64,6 +66,8 @@ export default function App() {
 
                     <Route path="/dev" component={Developer} />
                     <Route path="/friends" component={Friends} />
+                    <Route path="/open/:id" component={Open} />
+                    <Route path="/invite/:code" component={Invite} />
                     <Route path="/" component={Home} />
                 </Switch>
             </Routes>
@@ -75,15 +79,3 @@ export default function App() {
         </OverlappingPanels>
     );
 };
-
-/**
- * 
- * 
-                        
-                        <Route path="/open/:id">
-                            <Open />
-                        </Route>
-                        {/*<Route path="/invite/:code">
-                            <OpenInvite />
-                        </Route>
- */
diff --git a/src/pages/invite/Invite.module.scss b/src/pages/invite/Invite.module.scss
new file mode 100644
index 0000000000000000000000000000000000000000..58814516539e8c91e091675a8f89885d2f3c4b8b
--- /dev/null
+++ b/src/pages/invite/Invite.module.scss
@@ -0,0 +1,75 @@
+.invite {
+    height: 100%;
+    display: flex;
+    color: white;
+    user-select: none;
+    align-items: center;
+    flex-direction: column;
+    background-size: cover;
+    justify-content: center;
+    background-position: center;
+
+    * {
+        overflow: visible;
+    }
+
+    .icon {
+        width: 64px;
+        z-index: 100;
+        text-align: left;
+        position: relative;
+
+        > * {
+            top: -32px;
+            position: absolute;
+        }
+    }
+
+    .leave {
+        top: 8px;
+        left: 8px;
+        cursor: pointer;
+        position: fixed;
+    }
+
+    .details {
+        text-align: center;
+        border-radius: 3px;
+        align-self: center;
+        padding: 32px 16px 16px 16px;
+        background: rgba(0, 0, 0, 0.6);
+        
+        h1 {
+            margin: 0;
+            font-weight: 500;
+        }
+
+        h2 {
+            margin: 4px;
+            opacity: 0.7;
+            font-size: 0.8em;
+            font-weight: 400;
+        }
+
+        h3 {
+            gap: 8px;
+            display: flex;
+            font-size: 1em;
+            font-weight: 400;
+            flex-direction: row;
+            justify-content: center;
+        }
+
+        button {
+            margin: auto;
+            display: block;
+            background: rgba(0, 0, 0, 0.8);
+        }
+    }
+}
+
+.preloader {
+    height: 100%;
+    display: grid;
+    place-items: center;
+}
diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cb01eaf116983b8bdbcaa962436a69318a74098e
--- /dev/null
+++ b/src/pages/invite/Invite.tsx
@@ -0,0 +1,87 @@
+import styles from './Invite.module.scss';
+import Button from '../../components/ui/Button';
+import { ArrowLeft } from "@styled-icons/feather";
+import Overline from '../../components/ui/Overline';
+import { Invites } from "revolt.js/dist/api/objects";
+import Preloader from '../../components/ui/Preloader';
+import { takeError } from "../../context/revoltjs/util";
+import { useHistory, useParams } from "react-router-dom";
+import ServerIcon from '../../components/common/ServerIcon';
+import UserIcon from '../../components/common/user/UserIcon';
+import { useContext, useEffect, useState } from "preact/hooks";
+import RequiresOnline from '../../context/revoltjs/RequiresOnline';
+import { AppContext, ClientStatus, StatusContext } from "../../context/revoltjs/RevoltClient";
+
+export default function Invite() {
+    const history = useHistory();
+    const client = useContext(AppContext);
+    const status = useContext(StatusContext);
+    const { code } = useParams<{ code: string }>();
+    const [ processing, setProcessing ] = useState(false);
+    const [ error, setError ] = useState<string | undefined>(undefined);
+    const [ invite, setInvite ] = useState<Invites.RetrievedInvite | undefined>(undefined);
+
+    useEffect(() => {
+        if (typeof invite === 'undefined' && (status === ClientStatus.ONLINE || status === ClientStatus.READY)) {
+            client.fetchInvite(code)
+                .then(data => setInvite(data))
+                .catch(err => setError(takeError(err)))
+        }
+    }, [ status ]);
+
+    if (typeof invite === 'undefined') {
+        return (
+            <div className={styles.preloader}>
+                <RequiresOnline>
+                    { error ? <Overline type="error" error={error} />
+                        : <Preloader /> }
+                </RequiresOnline>
+            </div>
+        )
+    }
+
+    return (
+        <div className={styles.invite} style={{ backgroundImage: invite.server_banner ? `url('${client.generateFileURL(invite.server_banner)}')` : undefined }}>
+            <div className={styles.leave}>
+                <ArrowLeft size={32} onClick={() => history.push('/')} />
+            </div>
+
+            { !processing && 
+                <div className={styles.icon}>
+                    <ServerIcon attachment={invite.server_icon} server_name={invite.server_name} size={64} />
+                </div> }
+
+            <div className={styles.details}>
+                { processing ? <Preloader /> :
+                    <>
+                        <h1>{ invite.server_name }</h1>
+                        <h2>#{invite.channel_name}</h2>
+                        <h3>Invited by <UserIcon size={24} attachment={invite.user_avatar} /> { invite.user_name }</h3>
+                        <Overline type="error" error={error} />
+                        <Button contrast
+                            onClick={
+                                async () => {
+                                    if (status === ClientStatus.READY) {
+                                        return history.push('/');
+                                    }
+
+                                    try {
+                                        setProcessing(true);
+
+                                        let result = await client.joinInvite(code);
+                                        if (result.type === 'Server') {
+                                            history.push(`/server/${result.server._id}/channel/${result.channel._id}`);
+                                        }
+                                    } catch (err) {
+                                        setError(takeError(err));
+                                        setProcessing(false);
+                                    }
+                                }
+                            }
+                        >{ status === ClientStatus.READY ? 'Login to REVOLT' : 'Accept Invite' }</Button>
+                    </>
+                }
+            </div>
+        </div>
+    );
+}