diff --git a/resources/assets/components/Profile.vue b/resources/assets/components/Profile.vue
new file mode 100644
index 000000000..1dc90d0ba
--- /dev/null
+++ b/resources/assets/components/Profile.vue
@@ -0,0 +1,205 @@
+<template>
+    <div class="profile-timeline-component">
+        <div v-if="isLoaded" class="container-fluid mt-3">
+            <div class="row">
+                <div class="col-md-3 d-md-block px-md-3 px-xl-5">
+                    <profile-sidebar
+                        :profile="profile"
+                        :relationship="relationship"
+                        :user="curUser"
+                        v-on:back="goBack"
+                        v-on:toggletab="toggleTab"
+                        v-on:updateRelationship="updateRelationship"
+                        @follow="follow"
+                        @unfollow="unfollow" />
+                </div>
+
+                <div class="col-md-8 px-md-5">
+                    <component
+                        v-bind:is="getTabComponentName()"
+                        :key="getTabComponentName() + profile.id"
+                        :profile="profile"
+                        :relationship="relationship" />
+                </div>
+            </div>
+
+            <drawer />
+        </div>
+    </div>
+</template>
+
+<script type="text/javascript">
+    import Drawer from './partials/drawer.vue';
+    import ProfileFeed from './partials/profile/ProfileFeed.vue';
+    import ProfileSidebar from './partials/profile/ProfileSidebar.vue';
+    import ProfileFollowers from './partials/profile/ProfileFollowers.vue';
+    import ProfileFollowing from './partials/profile/ProfileFollowing.vue';
+
+    export default {
+        props: {
+            id: {
+                type: String
+            },
+
+            profileId: {
+                type: String
+            },
+
+            username: {
+                type: String
+            },
+
+            cachedProfile: {
+                type: Object
+            },
+
+            cachedUser: {
+                type: Object
+            }
+        },
+
+        components: {
+            "drawer": Drawer,
+            "profile-feed": ProfileFeed,
+            "profile-sidebar": ProfileSidebar,
+            "profile-followers": ProfileFollowers,
+            "profile-following": ProfileFollowing
+        },
+
+        data() {
+            return {
+                isLoaded: false,
+                curUser: undefined,
+                tab: "index",
+                profile: undefined,
+                relationship: undefined
+            }
+        },
+
+        mounted() {
+            this.init();
+        },
+
+        watch: {
+            '$route': 'init'
+        },
+
+        methods: {
+            init() {
+                this.tab = 'index';
+                this.isLoaded = false;
+                this.relationship = undefined;
+                this.owner = false;
+
+                if(this.cachedProfile && this.cachedUser) {
+                    this.curUser = this.cachedUser;
+                    this.profile = this.cachedProfile;
+                    // this.fetchPosts();
+                    // this.isLoaded = true;
+                    this.fetchRelationship();
+                } else {
+                    this.curUser = window._sharedData.user;
+                    this.fetchProfile();
+                }
+            },
+
+            getTabComponentName() {
+                switch(this.tab) {
+                    case 'index':
+                        return "profile-feed";
+                    break;
+
+                    default:
+                        return `profile-${this.tab}`;
+                    break;
+                }
+            },
+
+            fetchProfile() {
+                let id = this.profileId ? this.profileId : this.id;
+                axios.get('/api/pixelfed/v1/accounts/' + id)
+                .then(res => {
+                    this.profile = res.data;
+                    if(res.data.id == this.curUser.id) {
+                        this.owner = true;
+                        // this.isLoaded = true;
+                        // this.loaded();
+                        // this.fetchPosts();
+                        this.fetchRelationship();
+                    } else {
+                        this.owner = false;
+                        this.fetchRelationship();
+                    }
+                })
+                .catch(err => {
+                    this.$router.push('/i/web/404');
+                });
+            },
+
+            fetchRelationship() {
+                if(this.owner) {
+                    this.relationship = {};
+                    this.isLoaded = true;
+                    return;
+                }
+
+                axios.get('/api/v1/accounts/relationships', {
+                    params: {
+                        'id[]': this.profile.id
+                    }
+                }).then(res => {
+                    this.relationship = res.data[0];
+                    this.isLoaded = true;
+                })
+            },
+
+            toggleTab(tab) {
+                this.tab = tab;
+            },
+
+            goBack() {
+                this.$router.go(-1);
+            },
+
+            unfollow() {
+                axios.post('/api/v1/accounts/' + this.profile.id + '/unfollow')
+                .then(res => {
+                    this.$store.commit('updateRelationship', [res.data])
+                    this.relationship = res.data;
+                    if(this.profile.locked) {
+                        location.reload();
+                    }
+                    this.profile.followers_count--;
+                }).catch(err => {
+                    swal('Oops!', 'An error occured when attempting to unfollow this account.', 'error');
+                    this.relationship.following = true;
+                });
+            },
+
+            follow() {
+                axios.post('/api/v1/accounts/' + this.profile.id + '/follow')
+                .then(res => {
+                    this.$store.commit('updateRelationship', [res.data])
+                    this.relationship = res.data;
+                    if(this.profile.locked) {
+                        this.relationship.requested = true;
+                    }
+                    this.profile.followers_count++;
+                }).catch(err => {
+                    swal('Oops!', 'An error occured when attempting to follow this account.', 'error');
+                    this.relationship.following = false;
+                });
+            },
+
+            updateRelationship(val) {
+                this.relationship = val;
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .profile-timeline-component {
+        margin-bottom: 10rem;
+    }
+</style>
diff --git a/resources/assets/components/ProfileFollowers.vue b/resources/assets/components/ProfileFollowers.vue
new file mode 100644
index 000000000..ea5393445
--- /dev/null
+++ b/resources/assets/components/ProfileFollowers.vue
@@ -0,0 +1,127 @@
+<template>
+    <div class="profile-timeline-component">
+        <div v-if="isLoaded" class="container-fluid mt-3">
+            <div class="row">
+                <div class="col-12 col-md-8 offset-md-2 px-md-5">
+                    <profile-followers
+                        :profile="profile"
+                        :relationship="relationship"
+                        @back="goBack()"
+                    />
+                </div>
+            </div>
+
+            <drawer />
+        </div>
+    </div>
+</template>
+
+<script type="text/javascript">
+    import Drawer from './partials/drawer.vue';
+    import ProfileFollowers from './partials/profile/ProfileFollowers.vue';
+
+    export default {
+        props: {
+            id: {
+                type: String
+            },
+
+            profileId: {
+                type: String
+            },
+
+            username: {
+                type: String
+            },
+
+            cachedProfile: {
+                type: Object
+            },
+
+            cachedUser: {
+                type: Object
+            }
+        },
+
+        components: {
+            "drawer": Drawer,
+            "profile-followers": ProfileFollowers
+        },
+
+        data() {
+            return {
+                isLoaded: false,
+                curUser: undefined,
+                profile: undefined,
+                relationship: undefined
+            }
+        },
+
+        mounted() {
+            this.init();
+        },
+
+        watch: {
+            '$route': 'init'
+        },
+
+        methods: {
+            init() {
+                this.isLoaded = false;
+                this.relationship = undefined;
+                this.owner = false;
+
+                if(this.cachedProfile && this.cachedUser) {
+                    this.curUser = this.cachedUser;
+                    this.profile = this.cachedProfile;
+                    this.fetchRelationship();
+                } else {
+                    this.curUser = window._sharedData.user;
+                    this.fetchProfile();
+                }
+            },
+
+            fetchProfile() {
+                let id = this.profileId ? this.profileId : this.id;
+                axios.get('/api/pixelfed/v1/accounts/' + id)
+                .then(res => {
+                    this.profile = res.data;
+                    if(res.data.id == this.curUser.id) {
+                        this.owner = true;
+                        // this.isLoaded = true;
+                        // this.loaded();
+                        // this.fetchPosts();
+                        this.fetchRelationship();
+                    } else {
+                        this.owner = false;
+                        this.fetchRelationship();
+                    }
+                })
+                .catch(err => {
+                    this.$router.push('/i/web/404');
+                });
+            },
+
+            fetchRelationship() {
+                if(this.owner) {
+                    this.relationship = {};
+                    this.isLoaded = true;
+                    return;
+                }
+
+                axios.get('/api/v1/accounts/relationships', {
+                    params: {
+                        'id[]': this.profile.id
+                    }
+                }).then(res => {
+                    this.relationship = res.data[0];
+                    this.isLoaded = true;
+                })
+            },
+
+            goBack() {
+                this.$router.push('/i/web/profile/' + this.profile.id);
+            }
+        }
+    }
+</script>
diff --git a/resources/assets/components/ProfileFollowing.vue b/resources/assets/components/ProfileFollowing.vue
new file mode 100644
index 000000000..5f980ac77
--- /dev/null
+++ b/resources/assets/components/ProfileFollowing.vue
@@ -0,0 +1,124 @@
+<template>
+    <div class="profile-timeline-component">
+        <div v-if="isLoaded" class="container-fluid mt-3">
+            <div class="row">
+                <div class="col-12 col-md-8 offset-md-2 px-md-5">
+                    <profile-following
+                        :profile="profile"
+                        :relationship="relationship"
+                        @back="goBack()"
+                    />
+                </div>
+            </div>
+
+            <drawer />
+        </div>
+    </div>
+</template>
+
+<script type="text/javascript">
+    import Drawer from './partials/drawer.vue';
+    import ProfileFollowing from './partials/profile/ProfileFollowing.vue';
+
+    export default {
+        props: {
+            id: {
+                type: String
+            },
+
+            profileId: {
+                type: String
+            },
+
+            username: {
+                type: String
+            },
+
+            cachedProfile: {
+                type: Object
+            },
+
+            cachedUser: {
+                type: Object
+            }
+        },
+
+        components: {
+            "drawer": Drawer,
+            "profile-following": ProfileFollowing
+        },
+
+        data() {
+            return {
+                isLoaded: false,
+                curUser: undefined,
+                profile: undefined,
+                relationship: undefined
+            }
+        },
+
+        mounted() {
+            this.init();
+        },
+
+        watch: {
+            '$route': 'init'
+        },
+
+        methods: {
+            init() {
+                this.isLoaded = false;
+                this.relationship = undefined;
+                this.owner = false;
+
+                if(this.cachedProfile && this.cachedUser) {
+                    this.curUser = this.cachedUser;
+                    this.profile = this.cachedProfile;
+                    this.fetchRelationship();
+                } else {
+                    this.curUser = window._sharedData.user;
+                    this.fetchProfile();
+                }
+            },
+
+            fetchProfile() {
+                let id = this.profileId ? this.profileId : this.id;
+                axios.get('/api/pixelfed/v1/accounts/' + id)
+                .then(res => {
+                    this.profile = res.data;
+                    if(res.data.id == this.curUser.id) {
+                        this.owner = true;
+                        this.fetchRelationship();
+                    } else {
+                        this.owner = false;
+                        this.fetchRelationship();
+                    }
+                })
+                .catch(err => {
+                    this.$router.push('/i/web/404');
+                });
+            },
+
+            fetchRelationship() {
+                if(this.owner) {
+                    this.relationship = {};
+                    this.isLoaded = true;
+                    return;
+                }
+
+                axios.get('/api/v1/accounts/relationships', {
+                    params: {
+                        'id[]': this.profile.id
+                    }
+                }).then(res => {
+                    this.relationship = res.data[0];
+                    this.isLoaded = true;
+                })
+            },
+
+            goBack() {
+                this.$router.push('/i/web/profile/' + this.profile.id);
+            }
+        }
+    }
+</script>