Docker caches each step and reuses it until something that step depends on changes. COPY . . depends on every file in your project, so any edit invalidates it, and every step after it reruns, including the install.
Copy the dependency manifests on their own first, install, and only then copy the code:
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]
Now the install only reruns when the lockfile changes. Also add a .dockerignore containing at least node_modules and .git. Otherwise you copy your local node_modules over the freshly installed one, and the build context is huge too.