diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72a584ddb0..95d21ac05f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@ ChangeLog
 =========
 主に notable な changes を書いていきます
 
+unreleased
+----------
+* 投稿ページに次の投稿/前の投稿リンクを作成 (#734)
+
 2380
 ----
 アプリケーションが作れない問題を修正
diff --git a/locales/en.yml b/locales/en.yml
index 55a588f99f..9bf6446641 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -231,6 +231,10 @@ desktop:
       attaches: "{} media attached"
       uploading-media: "Uploading {} media"
 
+    mk-post-page:
+      prev: "Previous post"
+      next: "Next post"
+
     mk-timeline-post:
       reposted-by: "Reposted by {}"
       reply: "Reply"
diff --git a/locales/ja.yml b/locales/ja.yml
index e5b2beaed1..d2b282bff6 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -231,6 +231,10 @@ desktop:
       attaches: "添付: {}メディア"
       uploading-media: "{}個のメディアをアップロード中"
 
+    mk-post-page:
+      prev: "前の投稿"
+      next: "次の投稿"
+
     mk-timeline-post:
       reposted-by: "{}がRepost"
       reply: "返信"
diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts
index 3c96884dd1..13773bda9e 100644
--- a/src/api/serializers/post.ts
+++ b/src/api/serializers/post.ts
@@ -73,44 +73,79 @@ const self = (
 		));
 	}
 
-	if (_post.reply_to_id && opts.detail) {
-		// Populate reply to post
-		_post.reply_to = await self(_post.reply_to_id, me, {
-			detail: false
+	// When requested a detailed post data
+	if (opts.detail) {
+		// Get previous post info
+		const prev = await Post.findOne({
+			user_id: _post.user_id,
+			_id: {
+				$lt: id
+			}
+		}, {
+			fields: {
+				_id: true
+			},
+			sort: {
+				_id: -1
+			}
 		});
-	}
+		_post.prev = prev ? prev._id : null;
 
-	if (_post.repost_id && opts.detail) {
-		// Populate repost
-		_post.repost = await self(_post.repost_id, me, {
-			detail: _post.text == null
+		// Get next post info
+		const next = await Post.findOne({
+			user_id: _post.user_id,
+			_id: {
+				$gt: id
+			}
+		}, {
+			fields: {
+				_id: true
+			},
+			sort: {
+				_id: 1
+			}
 		});
-	}
+		_post.next = next ? next._id : null;
 
-	// Poll
-	if (me && _post.poll && opts.detail) {
-		const vote = await Vote
-			.findOne({
-				user_id: me._id,
-				post_id: id
+		if (_post.reply_to_id) {
+			// Populate reply to post
+			_post.reply_to = await self(_post.reply_to_id, me, {
+				detail: false
 			});
-
-		if (vote != null) {
-			_post.poll.choices.filter(c => c.id == vote.choice)[0].is_voted = true;
 		}
-	}
 
-	// Fetch my reaction
-	if (me && opts.detail) {
-		const reaction = await Reaction
-			.findOne({
-				user_id: me._id,
-				post_id: id,
-				deleted_at: { $exists: false }
+		if (_post.repost_id) {
+			// Populate repost
+			_post.repost = await self(_post.repost_id, me, {
+				detail: _post.text == null
 			});
+		}
 
-		if (reaction) {
-			_post.my_reaction = reaction.reaction;
+		// Poll
+		if (me && _post.poll) {
+			const vote = await Vote
+				.findOne({
+					user_id: me._id,
+					post_id: id
+				});
+
+			if (vote != null) {
+				_post.poll.choices.filter(c => c.id == vote.choice)[0].is_voted = true;
+			}
+		}
+
+		// Fetch my reaction
+		if (me) {
+			const reaction = await Reaction
+				.findOne({
+					user_id: me._id,
+					post_id: id,
+					deleted_at: { $exists: false }
+				});
+
+			if (reaction) {
+				_post.my_reaction = reaction.reaction;
+			}
 		}
 	}
 
diff --git a/src/web/app/desktop/tags/pages/post.tag b/src/web/app/desktop/tags/pages/post.tag
index c91e98bbd4..f270b43ac2 100644
--- a/src/web/app/desktop/tags/pages/post.tag
+++ b/src/web/app/desktop/tags/pages/post.tag
@@ -1,7 +1,9 @@
 <mk-post-page>
 	<mk-ui ref="ui">
-		<main>
+		<main if={ !parent.fetching }>
+			<a if={ parent.post.next } href={ parent.post.next }><i class="fa fa-angle-up"></i>%i18n:desktop.tags.mk-post-page.next%</a>
 			<mk-post-detail ref="detail" post={ parent.post }/>
+			<a if={ parent.post.prev } href={ parent.post.prev }><i class="fa fa-angle-down"></i>%i18n:desktop.tags.mk-post-page.prev%</a>
 		</main>
 	</mk-ui>
 	<style>
@@ -10,6 +12,19 @@
 
 			main
 				padding 16px
+				text-align center
+
+				> a
+					display inline-block
+
+					&:first-child
+						margin-bottom 4px
+
+					&:last-child
+						margin-top 4px
+
+					> i
+						margin-right 4px
 
 				> mk-post-detail
 					margin 0 auto
@@ -18,16 +33,23 @@
 	<script>
 		import Progress from '../../../common/scripts/loading';
 
-		this.post = this.opts.post;
+		this.mixin('api');
+
+		this.fetching = true;
+		this.post = null;
 
 		this.on('mount', () => {
 			Progress.start();
 
-			this.refs.ui.refs.detail.on('post-fetched', () => {
-				Progress.set(0.5);
-			});
+			this.api('posts/show', {
+				post_id: this.opts.post
+			}).then(post => {
+
+				this.update({
+					fetching: false,
+					post: post
+				});
 
-			this.refs.ui.refs.detail.on('loaded', () => {
 				Progress.done();
 			});
 		});
diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag
index b162a4084a..2a962816e1 100644
--- a/src/web/app/desktop/tags/post-detail.tag
+++ b/src/web/app/desktop/tags/post-detail.tag
@@ -1,8 +1,5 @@
 <mk-post-detail title={ title }>
-	<div class="fetching" if={ fetching }>
-		<mk-ellipsis-icon/>
-	</div>
-	<div class="main" if={ !fetching }>
+	<div class="main">
 		<button class="read-more" if={ p.reply_to && p.reply_to.reply_to_id && context == null } title="会話をもっと読み込む" onclick={ loadContext } disabled={ contextFetching }>
 			<i class="fa fa-ellipsis-v" if={ !contextFetching }></i>
 			<i class="fa fa-spinner fa-pulse" if={ contextFetching }></i>
@@ -71,13 +68,11 @@
 			padding 0
 			width 640px
 			overflow hidden
+			text-align left
 			background #fff
 			border solid 1px rgba(0, 0, 0, 0.1)
 			border-radius 8px
 
-			> .fetching
-				padding 64px 0
-
 			> .main
 
 				> .read-more
@@ -262,56 +257,41 @@
 		this.mixin('api');
 		this.mixin('user-preview');
 
-		this.fetching = true;
 		this.contextFetching = false;
 		this.context = null;
-		this.post = null;
+		this.post = this.opts.post;
+		this.isRepost = this.post.repost != null;
+		this.p = this.isRepost ? this.post.repost : this.post;
+		this.p.reactions_count = this.p.reaction_counts ? Object.keys(this.p.reaction_counts).map(key => this.p.reaction_counts[key]).reduce((a, b) => a + b) : 0;
+		this.title = dateStringify(this.p.created_at);
 
 		this.on('mount', () => {
-			this.api('posts/show', {
-				post_id: this.opts.post
-			}).then(post => {
-				const isRepost = post.repost != null;
-				const p = isRepost ? post.repost : post;
-				p.reactions_count = p.reaction_counts ? Object.keys(p.reaction_counts).map(key => p.reaction_counts[key]).reduce((a, b) => a + b) : 0;
+			if (this.p.text) {
+				const tokens = this.p.ast;
 
-				this.update({
-					fetching: false,
-					post: post,
-					isRepost: isRepost,
-					p: p,
-					title: dateStringify(p.created_at)
+				this.refs.text.innerHTML = compile(tokens);
+
+				this.refs.text.children.forEach(e => {
+					if (e.tagName == 'MK-URL') riot.mount(e);
 				});
 
-				this.trigger('loaded');
-
-				if (this.p.text) {
-					const tokens = this.p.ast;
-
-					this.refs.text.innerHTML = compile(tokens);
-
-					this.refs.text.children.forEach(e => {
-						if (e.tagName == 'MK-URL') riot.mount(e);
+				// URLをプレビュー
+				tokens
+				.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
+				.map(t => {
+					riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
+						url: t.url
 					});
+				});
+			}
 
-					// URLをプレビュー
-					tokens
-					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
-					.map(t => {
-						riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
-							url: t.url
-						});
-					});
-				}
-
-				// Get replies
-				this.api('posts/replies', {
-					post_id: this.p.id,
-					limit: 8
-				}).then(replies => {
-					this.update({
-						replies: replies
-					});
+			// Get replies
+			this.api('posts/replies', {
+				post_id: this.p.id,
+				limit: 8
+			}).then(replies => {
+				this.update({
+					replies: replies
 				});
 			});
 		});