самый правильный вариант - можешь используя docker executor собдирать с помощью kaniko, он умеет кешировать слои. packages.json часто меняться не будет, и для 95% сборок этот шаг будет скипнут, взят из кеша
https://docs.gitlab.com/ee/ci/docker/using_kaniko.html
либо есть 2 вариант, костыльный, но потребует от тебя меньше движений. можешь просто кешировать папку
node-moduleshttps://docs.gitlab.com/ee/ci/caching/ (так вроде она называется, да?), кеш инвалидировать по изменению лок-файла с пакетами. в пайплайне проверять, и если папка node-modules в проекте есть, кеш не инвалидирован, тогда скипать команду npm install
и перейди на докер executor, хотя бы для стейджа build и test, жить станет проще