My CI pipeline passed while Next.js was quietly trying to access DynamoDB during the build. Instead of giving CI AWS credentials, I followed the warning and found an architectural problem hiding behind a green check mark.
My CI pipeline was green. That should have been the end of it. Tests passed. TypeScript passed. Next.js finished building. GitHub gave me the check mark I wanted.
But buried in the build output was this:
text
Failed to list published articles.CredentialsProviderError:Could not load credentials from any providers
A few lines later:
text
✓ Next.js production build passed
Technically, CI was telling the truth. The build passed. But something was wrong.
and the warning probably disappears. But now the CI environment has another credential path to maintain. Another role to secure. Another policy to audit. Another place capable of reaching infrastructure.
And for what?
To support a database call that shouldn't have happened during the build in the first place. That's a bad trade.
Credentials should exist because a system needs them, not because they're convenient enough to make an error go away.
That made the problem much easier to reason about. The DynamoDB client wasn't randomly connecting to AWS.
Some application code was being evaluated during the build, and that code eventually crossed the persistence boundary.
At the time, I still had public article API routes such as:
text
/api/article/api/article/[slug]
But my public Server Components no longer needed them.
The pages already had a direct server-side path:
text
The public HTTP routes were effectively dead architecture. Nothing in the application needed to call them. Yet they still existed, and Next.js was touching enough of that surface during the production build to expose the unwanted dependency.
My development machine normally had valid AWS credentials through SSO, so simply running the build locally wasn't a meaningful test. A hidden DynamoDB request could succeed locally and still fail in GitHub Actions. So I logged out of AWS SSO.
Then I verified that my machine could no longer authenticate to AWS. Only after that did I run the same checks again. The question wasn't merely:
Does the build pass?
It was:
Does the build pass in an environment that cannot access AWS?
It did. And this time the logs were clean. That was the result I actually wanted.
CI systems ultimately care a lot about exit codes. If a process encounters an error, handles it, and eventually exits successfully, the pipeline can still be green. That's perfectly reasonable behavior.
But it means this:
text
✓ Build passed
doesn't necessarily mean:
text
Nothing suspicious happened.
In my case, the application tolerated the DynamoDB failure well enough for Next.js to complete the build. That made the pipeline technically successful. It didn't make the behavior correct.
I think about it like a Ferrari with a missing air filter. You turn the key. The engine starts. You drive it around the block.
It's fast. You could look at that and conclude:
The car works.
And technically, you'd be right.
But if you notice that missing air filter, you don't ignore it because the car still moves. The fact that the system can continue operating doesn't make every warning irrelevant.
CI is the same way. A green check mark tells me that the process completed successfully.
The biggest lesson wasn't about DynamoDB or Next.js. It was about resisting the easiest fix. When CI complained that it couldn't access AWS, granting CI access would have solved the immediate symptom.
Instead, asking why it wanted access at all exposed unnecessary application architecture.
The final system ended up with:
fewer routes,
fewer dependencies during the build,
no AWS credentials in CI,
a clearer server-side data path,
and cleaner build output.
All because I didn't ignore an error inside a successful build. A green pipeline is useful.
A green pipeline with clean logs is better.
next build ↓/api/article ↓listPublishedArticlesService ↓articleRepository.listPublished() ↓DynamoDB
Server Component ↓server query ↓application service ↓repository ↓DynamoDB