diff --git a/internal/config/config.go b/internal/config/config.go
index 3ee9d0b9783fe72dbc9960040b107f9081a1e857..fec1d68771aab1426aac01dda561858701fb52ce 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,6 +10,7 @@ type Config struct {
 	Version             string
 	DocumentRoot        string
 	DevelopmentMode     bool
+	WebpackAddr         string
 	Socket              string
 	ProxyHeadersTimeout time.Duration
 	APILimit            uint
diff --git a/internal/upstream/routes.go b/internal/upstream/routes.go
index 5c5232afb0c30bc65fa36592bb433e47ce8716dc..be1665fd23c40f2760220dde8deb4514bceed445 100644
--- a/internal/upstream/routes.go
+++ b/internal/upstream/routes.go
@@ -19,6 +19,7 @@ import (
 	"gitlab.com/gitlab-org/gitlab-workhorse/internal/staticpages"
 	"gitlab.com/gitlab-org/gitlab-workhorse/internal/terminal"
 	"gitlab.com/gitlab-org/gitlab-workhorse/internal/upload"
+	"gitlab.com/gitlab-org/gitlab-workhorse/internal/webpack"
 )
 
 type matcherFunc func(*http.Request) bool
@@ -139,6 +140,18 @@ func (u *Upstream) configureRoutes() {
 		route("", apiPattern, proxy),
 		route("", ciAPIPattern, proxy),
 
+		// In development mode, proxy /assets/webpack requests to webpack
+		// dev-server, otherwise serve static files from disk.
+		route(
+			"", `^/assets/webpack/`,
+			webpack.DevServer(u.DevelopmentMode, u.WebpackAddr,
+				static.ServeExisting(
+					u.URLPrefix,
+					staticpages.CacheExpireMax,
+					nil,
+				)),
+		),
+
 		// Serve assets
 		route(
 			"", `^/assets/`,
diff --git a/internal/webpack/devserver.go b/internal/webpack/devserver.go
new file mode 100644
index 0000000000000000000000000000000000000000..99c3900b50d12652ed8d3bdadd0f3518ed8cbfdf
--- /dev/null
+++ b/internal/webpack/devserver.go
@@ -0,0 +1,38 @@
+package webpack
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+
+	"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
+)
+
+func DevServer(enabled bool, address string, fallbackHandler http.Handler) http.Handler {
+	if !enabled {
+		return fallbackHandler
+	}
+
+	u, err := buildURL(address)
+	if err != nil {
+		panic(err)
+	}
+
+	return httputil.NewSingleHostReverseProxy(u)
+}
+
+func buildURL(address string) (*url.URL, error) {
+	u := helper.URLMustParse(address)
+	if u == nil {
+		return nil, fmt.Errorf("failed to parse URL in %q", address)
+	}
+
+	// Hope to support unix:// in the future
+	if u.Scheme != "tcp" {
+		return nil, fmt.Errorf("invalid scheme: %v", u)
+	}
+
+	u.Scheme = "http"
+	return u, nil
+}
diff --git a/internal/webpack/devserver_test.go b/internal/webpack/devserver_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2976688807a51865042cf2492752175d2ed8acf0
--- /dev/null
+++ b/internal/webpack/devserver_test.go
@@ -0,0 +1,33 @@
+package webpack
+
+import (
+	"testing"
+)
+
+func TestBuildURL(t *testing.T) {
+	examples := []struct {
+		input string
+		ok    bool
+	}{
+		{"", false},
+		{"localhost:5000", false},
+		{"tcp://localhost:5000", true},
+	}
+
+	for _, ex := range examples {
+		u, err := buildURL(ex.input)
+		if ex.ok {
+			if err != nil {
+				t.Errorf("example %v: expected no error, got %v", ex, err)
+			}
+			expectedScheme := "http"
+			if u.Scheme != expectedScheme {
+				t.Errorf("example %v: expected scheme %q, got %q", ex, expectedScheme, u.Scheme)
+			}
+		} else {
+			if err == nil {
+				t.Errorf("example %v: expected error, got none", ex)
+			}
+		}
+	}
+}
diff --git a/main.go b/main.go
index ce2013039f29fb13561b49901ce628163a0d1d77..b2ea90a4b27465ca0bb14b6444ef737d62369eb0 100644
--- a/main.go
+++ b/main.go
@@ -51,6 +51,7 @@ var apiQueueLimit = flag.Uint("apiQueueLimit", 0, "Number of API requests allowe
 var apiQueueTimeout = flag.Duration("apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests")
 var logFile = flag.String("logFile", "", "Log file to be used")
 var prometheusListenAddr = flag.String("prometheusListenAddr", "", "Prometheus listening address, e.g. ':9100'")
+var webpackAddr = flag.String("webpackAddr", "", "(development mode only) Network address of Webpack dev server, e.g. 'tcp://localhost:5000'")
 
 func main() {
 	flag.Usage = func() {
@@ -115,6 +116,7 @@ func main() {
 		Version:             Version,
 		DocumentRoot:        *documentRoot,
 		DevelopmentMode:     *developmentMode,
+		WebpackAddr:         *webpackAddr,
 		ProxyHeadersTimeout: *proxyHeadersTimeout,
 		APILimit:            *apiLimit,
 		APIQueueLimit:       *apiQueueLimit,