diff --git a/docs/.gitignore b/docs/.gitignore
index 55ec469a4..9cd1408bd 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -1,3 +1,4 @@
 public/
 templates/swagger/v1_json.tmpl
 themes/
+resources/
diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js
new file mode 100644
index 000000000..72d94c9ee
--- /dev/null
+++ b/docs/assets/js/search.js
@@ -0,0 +1,176 @@
+function ready(fn) {
+    if (document.readyState != 'loading') {
+        fn();
+    } else {
+        document.addEventListener('DOMContentLoaded', fn);
+    }
+}
+
+ready(doSearch);
+
+const summaryInclude = 60;
+const fuseOptions = {
+    shouldSort: true,
+    includeMatches: true,
+    matchAllTokens: true,
+    threshold: 0.0, // for parsing diacritics
+    tokenize: true,
+    location: 0,
+    distance: 100,
+    maxPatternLength: 32,
+    minMatchCharLength: 1,
+    keys: [{
+        name: "title",
+        weight: 0.8
+    },
+        {
+            name: "contents",
+            weight: 0.5
+        },
+        {
+            name: "tags",
+            weight: 0.3
+        },
+        {
+            name: "categories",
+            weight: 0.3
+        }
+    ]
+};
+
+function param(name) {
+    return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
+}
+
+let searchQuery = param("s");
+
+function doSearch() {
+    if (searchQuery) {
+        document.getElementById("search-query").value = searchQuery;
+        executeSearch(searchQuery);
+    } else {
+        const para = document.createElement("P");
+        para.innerText = "Please enter a word or phrase above";
+        document.getElementById("search-results").appendChild(para);
+    }
+}
+
+function getJSON(url, fn) {
+    const request = new XMLHttpRequest();
+    request.open('GET', url, true);
+    request.onload = function () {
+        if (request.status >= 200 && request.status < 400) {
+            const data = JSON.parse(request.responseText);
+            fn(data);
+        } else {
+            console.log("Target reached on " + url + " with error " + request.status);
+        }
+    };
+    request.onerror = function () {
+        console.log("Connection error " + request.status);
+    };
+    request.send();
+}
+
+function executeSearch(searchQuery) {
+    getJSON("/" + document.LANG + "/index.json", function (data) {
+        const pages = data;
+        const fuse = new Fuse(pages, fuseOptions);
+        const result = fuse.search(searchQuery);
+        console.log({
+            "matches": result
+        });
+        document.getElementById("search-results").innerHTML = "";
+        if (result.length > 0) {
+            populateResults(result);
+        } else {
+            const para = document.createElement("P");
+            para.innerText = "No matches found";
+            document.getElementById("search-results").appendChild(para);
+        }
+    });
+}
+
+function populateResults(result) {
+    result.forEach(function (value, key) {
+        const content = value.item.contents;
+        let snippet = "";
+        const snippetHighlights = [];
+        if (fuseOptions.tokenize) {
+            snippetHighlights.push(searchQuery);
+            value.matches.forEach(function (mvalue) {
+                if (mvalue.key === "tags" || mvalue.key === "categories") {
+                    snippetHighlights.push(mvalue.value);
+                } else if (mvalue.key === "contents") {
+                    const ind = content.toLowerCase().indexOf(searchQuery.toLowerCase());
+                    const start = ind - summaryInclude > 0 ? ind - summaryInclude : 0;
+                    const end = ind + searchQuery.length + summaryInclude < content.length ? ind + searchQuery.length + summaryInclude : content.length;
+                    snippet += content.substring(start, end);
+                    if (ind > -1) {
+                        snippetHighlights.push(content.substring(ind, ind + searchQuery.length))
+                    } else {
+                        snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
+                    }
+                }
+            });
+        }
+
+        if (snippet.length < 1) {
+            snippet += content.substring(0, summaryInclude * 2);
+        }
+        //pull template from hugo templarte definition
+        const templateDefinition = document.getElementById("search-result-template").innerHTML;
+        //replace values
+        const output = render(templateDefinition, {
+            key: key,
+            title: value.item.title,
+            link: value.item.permalink,
+            tags: value.item.tags,
+            categories: value.item.categories,
+            snippet: snippet
+        });
+        document.getElementById("search-results").appendChild(htmlToElement(output));
+
+        snippetHighlights.forEach(function (snipvalue) {
+            new Mark(document.getElementById("summary-" + key)).mark(snipvalue);
+        });
+
+    });
+}
+
+function render(templateString, data) {
+    let conditionalMatches, copy;
+    const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
+    //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
+    copy = templateString;
+    while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
+        if (data[conditionalMatches[1]]) {
+            //valid key, remove conditionals, leave content.
+            copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
+        } else {
+            //not valid, remove entire section
+            copy = copy.replace(conditionalMatches[0], '');
+        }
+    }
+    templateString = copy;
+    //now any conditionals removed we can do simple substitution
+    let key, find, re;
+    for (key in data) {
+        find = '\\$\\{\\s*' + key + '\\s*\\}';
+        re = new RegExp(find, 'g');
+        templateString = templateString.replace(re, data[key]);
+    }
+    return templateString;
+}
+
+/**
+ * By Mark Amery: https://stackoverflow.com/a/35385518
+ * @param {String} HTML representing a single element
+ * @return {Element}
+ */
+function htmlToElement(html) {
+    const template = document.createElement('template');
+    html = html.trim(); // Never return a text node of whitespace as the result
+    template.innerHTML = html;
+    return template.content.firstChild;
+}
diff --git a/docs/config.yaml b/docs/config.yaml
index 23d125733..039e2938f 100644
--- a/docs/config.yaml
+++ b/docs/config.yaml
@@ -20,6 +20,12 @@ params:
   website: https://docs.gitea.io
   version: 1.9.5
 
+outputs:
+  home:
+    - HTML
+    - RSS
+    - JSON
+
 menu:
   page:
     - name: Website
diff --git a/docs/content/doc/help.en-us.md b/docs/content/doc/help.en-us.md
index 5ad1dd7f1..635cb8931 100644
--- a/docs/content/doc/help.en-us.md
+++ b/docs/content/doc/help.en-us.md
@@ -2,12 +2,12 @@
 date: "2017-01-20T15:00:00+08:00"
 title: "Help"
 slug: "help"
-weight: 50
+weight: 5
 toc: false
 draft: false
 menu:
   sidebar:
     name: "Help"
-    weight: 50
+    weight: 5
     identifier: "help"
 ---
diff --git a/docs/content/doc/help.fr-fr.md b/docs/content/doc/help.fr-fr.md
new file mode 100644
index 000000000..ab0cedccf
--- /dev/null
+++ b/docs/content/doc/help.fr-fr.md
@@ -0,0 +1,13 @@
+---
+date: "2017-01-20T15:00:00+08:00"
+title: "Aide"
+slug: "help"
+weight: 5
+toc: false
+draft: false
+menu:
+  sidebar:
+    name: "Aide"
+    weight: 5
+    identifier: "help"
+---
diff --git a/docs/content/doc/help.zh-cn.md b/docs/content/doc/help.zh-cn.md
index 6af7aa171..9465cd546 100644
--- a/docs/content/doc/help.zh-cn.md
+++ b/docs/content/doc/help.zh-cn.md
@@ -2,12 +2,12 @@
 date: "2017-01-20T15:00:00+08:00"
 title: "帮助"
 slug: "help"
-weight: 50
+weight: 5
 toc: false
 draft: false
 menu:
   sidebar:
     name: "帮助"
-    weight: 50
+    weight: 5
     identifier: "help"
 ---
diff --git a/docs/content/doc/help.zh-tw.md b/docs/content/doc/help.zh-tw.md
new file mode 100644
index 000000000..c9cd794d8
--- /dev/null
+++ b/docs/content/doc/help.zh-tw.md
@@ -0,0 +1,13 @@
+---
+date: "2017-01-20T15:00:00+08:00"
+title: "救命"
+slug: "help"
+weight: 5
+toc: false
+draft: false
+menu:
+  sidebar:
+    name: "救命"
+    weight: 5
+    identifier: "help"
+---
diff --git a/docs/content/doc/help/search.en-us.md b/docs/content/doc/help/search.en-us.md
new file mode 100644
index 000000000..93c154bde
--- /dev/null
+++ b/docs/content/doc/help/search.en-us.md
@@ -0,0 +1,25 @@
+---
+date: "2019-11-12T16:00:00+02:00"
+title: "Search"
+slug: "search"
+weight: 4
+toc: true
+draft: false
+menu:
+  sidebar:
+    parent: "help"
+    name: "Search"
+    weight: 4
+    identifier: "search"
+sitemap:
+  priority : 0.1
+layout: "search"
+---
+
+
+This file exists solely to respond to /search URL with the related `search` layout template.
+
+No content shown here is rendered, all content is based in the template layouts/doc/search.html
+
+Setting a very low sitemap priority will tell search engines this is not important content.
+
diff --git a/docs/content/doc/help/search.fr-fr.md b/docs/content/doc/help/search.fr-fr.md
new file mode 100644
index 000000000..3507e9efe
--- /dev/null
+++ b/docs/content/doc/help/search.fr-fr.md
@@ -0,0 +1,25 @@
+---
+date: "2019-11-12T16:00:00+02:00"
+title: "Chercher"
+slug: "search"
+weight: 4
+toc: true
+draft: false
+menu:
+  sidebar:
+    parent: "help"
+    name: "Chercher"
+    weight: 4
+    identifier: "search"
+sitemap:
+  priority : 0.1
+layout: "search"
+---
+
+
+This file exists solely to respond to /search URL with the related `search` layout template.
+
+No content shown here is rendered, all content is based in the template layouts/doc/search.html
+
+Setting a very low sitemap priority will tell search engines this is not important content.
+
diff --git a/docs/content/doc/help/search.zh-cn.md b/docs/content/doc/help/search.zh-cn.md
new file mode 100644
index 000000000..a51860aac
--- /dev/null
+++ b/docs/content/doc/help/search.zh-cn.md
@@ -0,0 +1,25 @@
+---
+date: "2019-11-12T16:00:00+02:00"
+title: "搜索"
+slug: "search"
+weight: 4
+toc: true
+draft: false
+menu:
+  sidebar:
+    parent: "help"
+    name: "搜索"
+    weight: 4
+    identifier: "search"
+sitemap:
+  priority : 0.1
+layout: "search"
+---
+
+
+This file exists solely to respond to /search URL with the related `search` layout template.
+
+No content shown here is rendered, all content is based in the template layouts/doc/search.html
+
+Setting a very low sitemap priority will tell search engines this is not important content.
+
diff --git a/docs/content/doc/help/search.zh-tw.md b/docs/content/doc/help/search.zh-tw.md
new file mode 100644
index 000000000..a51860aac
--- /dev/null
+++ b/docs/content/doc/help/search.zh-tw.md
@@ -0,0 +1,25 @@
+---
+date: "2019-11-12T16:00:00+02:00"
+title: "搜索"
+slug: "search"
+weight: 4
+toc: true
+draft: false
+menu:
+  sidebar:
+    parent: "help"
+    name: "搜索"
+    weight: 4
+    identifier: "search"
+sitemap:
+  priority : 0.1
+layout: "search"
+---
+
+
+This file exists solely to respond to /search URL with the related `search` layout template.
+
+No content shown here is rendered, all content is based in the template layouts/doc/search.html
+
+Setting a very low sitemap priority will tell search engines this is not important content.
+
diff --git a/docs/layouts/_default/index.json b/docs/layouts/_default/index.json
new file mode 100644
index 000000000..ae08324d8
--- /dev/null
+++ b/docs/layouts/_default/index.json
@@ -0,0 +1,5 @@
+{{- $.Scratch.Add "index" slice -}}
+{{- range .Site.RegularPages -}}
+{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}}
+{{- end -}}
+{{- $.Scratch.Get "index" | jsonify -}}
diff --git a/docs/layouts/doc/search.html b/docs/layouts/doc/search.html
new file mode 100644
index 000000000..736fcaee1
--- /dev/null
+++ b/docs/layouts/doc/search.html
@@ -0,0 +1,44 @@
+{{ partial "header.html" . }}
+{{ partial "navbar.html" . }}
+
+<section class="section">
+	<div class="container is-centered page">
+		<div class="columns">
+			<div class="column is-one-quarter">
+                {{ partial "menu" . }}
+			</div>
+			<div class="column">
+				<div class=" content">
+					<section class="resume-section p-3 p-lg-5 d-flex flex-column">
+						<div class="my-auto" >
+							<form action="{{ "search" | absLangURL }}">
+								<label>Search:
+									<input id="search-query" name="s"/>
+								</label>
+							</form>
+							<br/>
+							<div id="search-results"></div>
+						</div>
+					</section>
+					<!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style -->
+					<script id="search-result-template" type="text/x-js-template">
+						<div id="summary-${key}">
+							<h4><a href="${link}">${title}</a></h4>
+							<p>${snippet}</p>
+							${ isset tags }<p>Tags: ${tags}</p>${ end }
+							${ isset categories }<p>Categories: ${categories}</p>${ end }
+							<hr/>
+						</div>
+					</script>
+				</div>
+			</div>
+		</div>
+	</div>
+</section>
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.4.5/fuse.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"></script>
+<script>document.LANG = "{{ .Language.Lang }}";</script>
+{{ $script := resources.Get "js/search.js" | minify | fingerprint -}}
+<script src="{{ $script.Permalink }}" {{ printf "integrity=%q" $script.Data.Integrity | safeHTMLAttr }}></script>
+{{ partial "footer.html" . }}