COPY . . invalidates the cache for every layer beneath it whenever any file changes — including your one component. So npm ci reruns every time.
Copy the manifests first, install, then copy the source:
Now the install layer is only invalidated when the lockfile changes.
Add a .dockerignore too — without it node_modules and .next get sent to the daemon and can bust the cache through mtime changes alone: