diff --git a/gulpfile.ts b/gulpfile.ts
index df355e33e784f58099f27c8faaac614d88f9e2db..a9ccbbdb5e256b7a15b1466e9253bcb21b8b764e 100644
--- a/gulpfile.ts
+++ b/gulpfile.ts
@@ -59,9 +59,15 @@ gulp.task('build:ts', () => {
 		.pipe(gulp.dest('./built/'));
 });
 
-gulp.task('build:copy', () =>
+gulp.task('build:copy:views', () =>
+	gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
+);
+
+gulp.task('build:copy', ['build:copy:views'], () =>
 	gulp.src([
 		'./build/Release/crypto_key.node',
+		'./src/const.json',
+		'./src/server/web/views/**/*',
 		'./src/**/assets/**/*',
 		'!./src/client/app/**/assets/**/*'
 	]).pipe(gulp.dest('./built/'))
diff --git a/package.json b/package.json
index 73369b26f354d540e7acd681d9dedfa94299a8d6..d4776a778ceca7fba0871ec1e87a4b80f16d005a 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
 		"@types/koa-multer": "1.0.0",
 		"@types/koa-router": "7.0.28",
 		"@types/koa-send": "4.1.1",
+		"@types/koa-views": "^2.0.3",
 		"@types/koa__cors": "2.2.2",
 		"@types/kue": "0.11.8",
 		"@types/license-checker": "15.0.0",
@@ -146,6 +147,7 @@
 		"koa-router": "7.4.0",
 		"koa-send": "4.1.3",
 		"koa-slow": "2.1.0",
+		"koa-views": "^6.1.4",
 		"kue": "0.11.6",
 		"license-checker": "18.0.0",
 		"loader-utils": "1.1.0",
diff --git a/src/client/app/base.pug b/src/client/app/base.pug
index 32a95a6c9971f104b277fa765949c33bb2c81810..c182fd6f64a9fa6a2634d3f1937488a319eff417 100644
--- a/src/client/app/base.pug
+++ b/src/client/app/base.pug
@@ -1,3 +1,5 @@
+block vars
+
 doctype html
 
 != '\n<!-- Thank you for using Misskey! @syuilo -->\n'
@@ -9,9 +11,17 @@ html
 		meta(name='application-name' content='Misskey')
 		meta(name='theme-color' content=themeColor)
 		meta(name='referrer' content='origin')
+		meta(property='og:site_name' content='Misskey')
 		link(rel='manifest' href='/manifest.json')
 
-		title Misskey
+		title
+			block title
+				| Misskey
+
+		block desc
+			meta(name='description' content='A SNS')
+
+		block meta
 
 		style
 			include ./../../../built/client/assets/init.css
diff --git a/src/renderers/get-note-summary.ts b/src/renderers/get-note-summary.ts
index dfc05ebfd62bcb7ab503fdd23a5aaf3fdd6aec11..0844c0b1846e41e5b9b0138f3fcaf3342d5420f8 100644
--- a/src/renderers/get-note-summary.ts
+++ b/src/renderers/get-note-summary.ts
@@ -1,6 +1,6 @@
 /**
  * 投稿を表す文字列を取得します。
- * @param {*} note 投稿
+ * @param {*} note (packされた)投稿
  */
 const summarize = (note: any): string => {
 	if (note.isHidden) {
diff --git a/src/server/web/index.ts b/src/server/web/index.ts
index 13751835bec9a67189a481f5f32752712730e3c2..7d4f76665dcd272eac19e927ec70e3bb4ecbdaa9 100644
--- a/src/server/web/index.ts
+++ b/src/server/web/index.ts
@@ -7,14 +7,32 @@ import * as Koa from 'koa';
 import * as Router from 'koa-router';
 import * as send from 'koa-send';
 import * as favicon from 'koa-favicon';
+import * as views from 'koa-views';
 
 import docs from './docs';
+import User from '../../models/user';
+import parseAcct from '../../acct/parse';
+import { fa } from '../../build/fa';
+import config from '../../config';
+import Note, { pack as packNote } from '../../models/note';
+import getNoteSummary from '../../renderers/get-note-summary';
+const consts = require('../../const.json');
 
 const client = `${__dirname}/../../client/`;
 
 // Init app
 const app = new Koa();
 
+// Init renderer
+app.use(views(__dirname + '/views', {
+	extension: 'pug',
+	options: {
+		config,
+		themeColor: consts.themeColor,
+		facss: fa.dom.css()
+	}
+}));
+
 // Serve favicon
 app.use(favicon(`${client}/assets/favicon.ico`));
 
@@ -67,6 +85,38 @@ router.use('/docs', docs.routes());
 // URL preview endpoint
 router.get('/url', require('./url-preview'));
 
+//#region for crawlers
+// User
+router.get('/@:user', async ctx => {
+	const { username, host } = parseAcct(ctx.params.user);
+	const user = await User.findOne({
+		usernameLower: username.toLowerCase(),
+		host
+	});
+
+	if (user != null) {
+		await ctx.render('user', { user });
+	} else {
+		ctx.status = 404;
+	}
+});
+
+// Note
+router.get('/notes/:note', async ctx => {
+	const note = await Note.findOne({ _id: ctx.params.note });
+
+	if (note != null) {
+		const _note = await packNote(note);
+		await ctx.render('note', {
+			note: _note,
+			summary: getNoteSummary(_note)
+		});
+	} else {
+		ctx.status = 404;
+	}
+});
+//#endregion
+
 // Render base html for all requests
 router.get('*', async ctx => {
 	await send(ctx, `app/base.html`, {
diff --git a/src/server/web/views/note.pug b/src/server/web/views/note.pug
new file mode 100644
index 0000000000000000000000000000000000000000..3107c0329cc6049858e8f7652e7be79438fbd13d
--- /dev/null
+++ b/src/server/web/views/note.pug
@@ -0,0 +1,20 @@
+extends ../../../../src/client/app/base
+
+block vars
+	- const user = note.user;
+	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
+	- const url = `${config.url}/notes/${note.id}`;
+	- const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
+
+block title
+	= `${title} | Misskey`
+
+block desc
+	meta(name='description' content= summary)
+
+block meta
+	meta(name='twitter:card'       content='summary')
+	meta(property='og:title'       content= title)
+	meta(property='og:description' content= summary)
+	meta(property='og:url'         content= url)
+	meta(property='og:image'       content= img)
diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug
new file mode 100644
index 0000000000000000000000000000000000000000..cdfacb32b4361eb3dbd1cf01d94234f5778e24f4
--- /dev/null
+++ b/src/server/web/views/user.pug
@@ -0,0 +1,19 @@
+extends ../../../../src/client/app/base
+
+block vars
+	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
+	- const url = config.url + '/@' + (user.host ? `${user.username}@${user.host}` : user.username);
+	- const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
+
+block title
+	= `${title} | Misskey`
+
+block desc
+	meta(name='description' content= user.description)
+
+block meta
+	meta(name='twitter:card'       content='summary')
+	meta(property='og:title'       content= title)
+	meta(property='og:description' content= user.description)
+	meta(property='og:url'         content= url)
+	meta(property='og:image'       content= img)