I recently ran into a frustrating error while building a Next.js 13 app. I was using axios to fetch API data inside getStaticProps()
, but during the build, my app threw a “Module not found: Can’t resolve ‘debug’ in ‘…/node_modules/follow-redirects’” error.
At first, I thought reinstalling packages would fix it. But the deeper I dug, I realized there were actually two separate issues:
- A resolver error involving
follow-redirects → debug
. - Incorrect usage of
getStaticProps()
—and even the wrong data-fetching model if you’re on the new App Router.
Let me break down my journey, the fixes, and some “bonus practice” functionality that made my project more resilient.
The First Code
Here’s the original code I wrote inside getStaticProps
:
// First attempt (WRONG)
export async function getStaticProps() {
await axios.get('https://my-api-url.com/getlist')
.then(response => {
setData(response.data);
setLoading(false);
})
.catch(error => {
console.error('Error fetching data:', error);
setLoading(false);
});
const data = await response.json();
console.log(data);
}
What Wrong Here
- The “Module not found” error
axios
uses the Node adapterfollow-redirects
, which has an optional dependency ondebug
. In some Webpack/Next.js builds, that optional dependency still gets resolved at build time. Ifdebug
isn’t installed, you’ll see this error. - Misuse of
getStaticProps
getStaticProps
runs on the server at build time, so I can’t call React state setters likesetData
orsetLoading
.- I also mistakenly mixed axios with
response.json()
, which only works withfetch
.
- App Router vs Pages Router confusion
My stack trace pointed tosrc/app/page.js
, meaning I was on the App Router. In that case,getStaticProps
doesn’t even exist I should be usingfetch
in Server Components instead.
Fix the Build Error
The first thing I did was fix the follow-redirects
error. Here are the commands I ran:
# Install the optional peer dependency
npm i debug
# Update axios
npm i axios@latest
# Clean and rebuild
rm -rf .next node_modules package-lock.json
npm i
npm run build
In most cases, installing debug
clears the resolver problem. If you’re targeting the Edge Runtime, though, axios won’t work (it depends on Node APIs). In that case, I recommend switching to fetch
.
Correct Data Fetching Pattern
The right fix depends on whether you’re in the Pages Router (pages/
folder) or the App Router (src/app/
folder).
Pages Router (with getStaticProps
)
// pages/index.js
import axios from 'axios';
export async function getStaticProps() {
const { data } = await axios.get('https://my-api-url.com/getlist');
return {
props: { initialData: data },
revalidate: 3600, // re-build every hour
};
}
export default function Home({ initialData }) {
return (
<main>
<h1>Home</h1>
<pre>{JSON.stringify(initialData, null, 2)}</pre>
</main>
);
}
✅ No setState
calls.
✅ Return props
.
✅ Use revalidate
for ISR (Incremental Static Regeneration).
App Router (no getStaticProps
support)
If you’re using the App Router, you should use Server Components with fetch
.
Use fetch
(recommended)
// src/app/page.js
export const revalidate = 3600;
export default async function Page() {
const res = await fetch('https://my-api-url.com/getlist');
if (!res.ok) {
return <div>Failed to load data.</div>;
}
const data = await res.json();
return (
<main>
<h1>Home</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</main>
);
}
Stick with axios (Node.js runtime only)
// src/app/page.js
import axios from 'axios';
export const revalidate = 3600;
export default async function Page() {
const { data } = await axios.get('https://my-api-url.com/getlist');
return (
<main>
<h1>Home</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</main>
);
}
Bonus Practice Enhancement
Once I got the basics working, I decided to add more robust functionality to my project.
Axios Helper with Retry + Timeout
// src/lib/http.js
import axios from 'axios';
export const http = axios.create({
timeout: 10_000,
});
http.interceptors.response.use(
(res) => res,
async (error) => {
const cfg = error.config;
if (!cfg) throw error;
cfg.__retryCount = cfg.__retryCount ?? 0;
const status = error.response?.status;
const shouldRetry =
cfg.method?.toLowerCase() === 'get' &&
cfg.__retryCount < 2 &&
(!status || status >= 500);
if (!shouldRetry) throw error;
cfg.__retryCount++;
await new Promise((r) => setTimeout(r, 500 * cfg.__retryCount));
return http(cfg);
}
);
Now, transient network issues don’t break my build.
Validate Responses with Zod
import { z } from 'zod';
const Item = z.object({
id: z.number(),
name: z.string(),
});
const ApiSchema = z.array(Item);
const data = ApiSchema.parse(await res.json());
This gave me confidence that my API returned what I expected.
Client Side Fallback with SWR
'use client';
import useSWR from 'swr';
import axios from 'axios';
const fetcher = (url) => axios.get(url).then((r) => r.data);
export default function ListClient() {
const { data, error, isLoading, mutate } = useSWR(
'https://my-api-url.com/getlist',
fetcher,
{ revalidateOnFocus: false }
);
if (isLoading) return <p>Loading…</p>;
if (error) return (
<div>
<p>Failed to load. {error.message}</p>
<button onClick={() => mutate()}>Retry</button>
</div>
);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
With this, users get a retry button instead of a blank error screen.
Final Thought
Fixing the “Module not found” error in Next.js taught me that most issues aren’t just about missing packages they’re about understanding how the framework expects you to fetch data. By installing the missing dependency, using the right data-fetching strategy (Pages vs App Router), and adding small enhancements like retries and validation, I not only solved the error but also made my app more reliable and easier to maintain.