diff --git a/.air.conf b/.air.conf
new file mode 100644
index 000000000..7b5a5cb86
--- /dev/null
+++ b/.air.conf
@@ -0,0 +1,9 @@
+root = "."
+tmp_dir = ".air"
+
+[build]
+cmd = "make backend"
+bin = "gitea"
+include_ext = ["go", "tmpl"]
+exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
+include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
diff --git a/.gitignore b/.gitignore
index 427d5dee3..0223bb994 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,7 @@ coverage.all
 /public/fonts
 /web_src/fomantic/build
 /VERSION
+/.air
 
 # Snapcraft
 snap/.snapcraft/
diff --git a/Makefile b/Makefile
index 3ab86245c..b1acbe229 100644
--- a/Makefile
+++ b/Makefile
@@ -106,6 +106,8 @@ BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
 
 SVG_DEST_DIR := public/img/svg
 
+AIR_TMP_DIR := .air
+
 TAGS ?=
 TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
 TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
@@ -161,6 +163,7 @@ help:
 	@echo " - lint-frontend                    lint frontend files"
 	@echo " - lint-backend                     lint backend files"
 	@echo " - watch-frontend                   watch frontend files and continuously rebuild"
+	@echo " - watch-backend                    watch backend files and continuously rebuild"
 	@echo " - webpack                          build webpack files"
 	@echo " - svg                              build svg files"
 	@echo " - fomantic                         build fomantic files"
@@ -306,6 +309,13 @@ watch-frontend: node-check $(FOMANTIC_DEST) node_modules
 	rm -rf $(WEBPACK_DEST_ENTRIES)
 	NODE_ENV=development npx webpack --hide-modules --display-entrypoints=false --watch --progress
 
+.PHONY: watch-backend
+watch-backend: go-check
+	@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+		GO111MODULE=off $(GO) get -u github.com/cosmtrek/air; \
+	fi
+	air -c .air.conf
+
 .PHONY: test
 test:
 	$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='sqlite sqlite_unlock_notify' $(GO_PACKAGES)
@@ -579,7 +589,7 @@ release-compress: | $(DIST_DIRS)
 .PHONY: release-sources
 release-sources: | $(DIST_DIRS) node_modules
 	echo $(VERSION) > $(STORED_VERSION_FILE)
-	tar --exclude=./$(DIST) --exclude=./.git --exclude=./$(MAKE_EVIDENCE_DIR) --exclude=./node_modules/.cache -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
+	tar --exclude=./$(DIST) --exclude=./.git --exclude=./$(MAKE_EVIDENCE_DIR) --exclude=./node_modules/.cache --exclude=./$(AIR_TMP_DIR) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
 	rm -f $(STORED_VERSION_FILE)
 
 .PHONY: release-docs
diff --git a/docs/content/doc/advanced/hacking-on-gitea.en-us.md b/docs/content/doc/advanced/hacking-on-gitea.en-us.md
index 5c3f19994..b24260c68 100644
--- a/docs/content/doc/advanced/hacking-on-gitea.en-us.md
+++ b/docs/content/doc/advanced/hacking-on-gitea.en-us.md
@@ -91,7 +91,23 @@ The simplest recommended way to build from source is:
 TAGS="bindata sqlite sqlite_unlock_notify" make build
 ```
 
-See `make help` for all available `make` tasks. Also see [`.drone.yml`](https://github.com/go-gitea/gitea/blob/master/.drone.yml) to see how our continuous integration works.
+The `build` target will execute both `frontend` and `backend` sub-targets. If the `bindata` tag is present, the frontend files will be compiled into the binary. It is recommended to leave out the tag when doing frontend development so that changes will be reflected.
+
+See `make help` for all available `make` targets. Also see [`.drone.yml`](https://github.com/go-gitea/gitea/blob/master/.drone.yml) to see how our continuous integration works.
+
+## Building continuously
+
+Both the `frontend` and `backend` targets can be ran continuously when source files change:
+
+````bash
+# in your first terminal
+make watch-backend
+
+# in your second terminal
+make watch-frontend
+````
+
+On macOS, watching all backend source files may hit the default open files limit which can be increased via `ulimit -n 12288` for the current shell or in your shell startup file for all future shells.
 
 ### Formatting, code analysis and spell check
 
@@ -123,26 +139,12 @@ make revive vet misspell-check
 
 ### Working on JS and CSS
 
-For simple changes, edit files in `web_src`, run the build and start the server to test:
+Either use the `watch-frontend` target mentioned above or just build once:
 
 ```bash
 make build && ./gitea
 ```
 
-`make build` runs both `make frontend` and `make backend` which can be run individually as well as long as the `bindata` tag is not used (which compiles frontend files into the binary).
-
-For more involved changes use the `watch-frontend` task to continuously rebuild files when their sources change. The `bindata` tag must be absent. First, build and run the backend:
-
-```bash
-make backend && ./gitea
-```
-
-With the backend running, open another terminal and run:
-
-```bash
-make watch-frontend
-```
-
 Before committing, make sure the linters pass:
 
 ```bash