diff --git a/.env.default b/.env.default
index eff729121..419b334b9 100644
--- a/.env.default
+++ b/.env.default
@@ -18,6 +18,7 @@ IMAGE_REDIS=redis:5-alpine
 IMAGE_DRIVER=zenika/alpine-chrome
 CLEAR_FRONT_PACKAGES=no
 ADD_PHP_EXT=
+YARN_CACHE_FOLDER=.cache/yarn
 MAIN_DOMAIN_NAME=docker.localhost
 DB_URL=sqlite:./../.cache/d8.sqlite
 # Faster but data will be lost on php container recreation
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dbdc22c30..137198143 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,6 +5,7 @@
 variables:
   GIT_DEPTH: "3"
   THEME_PATH: "" # Update to enable front jobs (web/themes/custom/XXX)
+  YARN_CACHE_FOLDER: ".cache/yarn"
   STORYBOOK_PATH: "" # Update to enable storybook job (themes/custom/XXX/dist/storybook/index.html)
   GIT_STRATEGY: clone # Workaround until git is updated in runner, see https://gitlab.com/gitlab-org/gitlab-foss/issues/60466
   REVIEW_DOMAIN: "XXX.XXX.com" # Mandatory, should equal to DNS of available runner server with docker + docker-compose + traefik
@@ -21,9 +22,18 @@ image: skilldlabs/php:73
     - branches
 
 .only_var_theme: &only_var_theme
+  cache:
+    key:
+      files:
+      # Until https://gitlab.com/gitlab-org/gitlab/issues/118466 lands ...
+        - web/themes/custom/XXX/package.json # ... this path has to be hardcoded
+        - web/themes/custom/XXX/yarn.lock # ... this path has to be hardcoded
+    paths:
+      - ${THEME_PATH}/${YARN_CACHE_FOLDER}/ # Populated during yarn install
   only:
     variables:
       - $THEME_PATH
+      - $YARN_CACHE_FOLDER
 
 before_script:
   - date
@@ -58,14 +68,6 @@ sniffers:front:
   script:
     - make front-install # Dependencies are required for js imports to pass linters
     - make lintval
-  cache:
-    key:
-      files:
-      # Until https://gitlab.com/gitlab-org/gitlab/issues/118466 lands ...
-        - web/themes/custom/XXX/package.json # ... this path has to be hardcoded
-        - web/themes/custom/XXX/yarn.lock # ... this path has to be hardcoded
-    paths:
-      - ${THEME_PATH}/node_modules/ # Populated during yarn install
   artifacts:
     name: "$CI_COMMIT_REF_NAME:$CI_COMMIT_SHA:front"
     expire_in: 1d
diff --git a/scripts/makefile/front.mk b/scripts/makefile/front.mk
index a943aceb6..44b0920bb 100644
--- a/scripts/makefile/front.mk
+++ b/scripts/makefile/front.mk
@@ -4,6 +4,7 @@ FRONT_PORT?=65200
 frontexec = docker run \
 	--rm \
 	--init \
+	--env YARN_CACHE_FOLDER=/app/$(YARN_CACHE_FOLDER) \
 	-u $(CUID):$(CGID) \
 	-v $(CURDIR)/web/themes/custom/$(THEME_NAME):/app \
 	--workdir /app \
@@ -13,6 +14,7 @@ frontexec = docker run \
 frontexec-with-port = docker run \
 	--rm \
 	--init \
+	--env YARN_CACHE_FOLDER=/app/$(YARN_CACHE_FOLDER) \
 	-p $(FRONT_PORT):$(FRONT_PORT) \
 	-u $(CUID):$(CGID) \
 	-v $(CURDIR)/web/themes/custom/$(THEME_NAME):/app \
@@ -23,6 +25,7 @@ frontexec-with-port = docker run \
 frontexec-with-interactive = docker run \
 	--rm \
 	--init \
+	--env YARN_CACHE_FOLDER=/app/$(YARN_CACHE_FOLDER) \
 	-u $(CUID):$(CGID) \
 	-v $(CURDIR)/web/themes/custom/$(THEME_NAME):/app \
 	--workdir /app \
@@ -53,6 +56,7 @@ front-build:
 		docker pull $(IMAGE_FRONT); \
 		$(call frontexec, node -v); \
 		$(call frontexec, yarn -v); \
+		$(call frontexec, yarn install --ignore-optional --check-files --prod); \
 		$(call frontexec, yarn build --verbose); \
 	else \
 		echo "- Theme directory defined in .env file was not found. Skipping front-build."; \