Missing files in next.js api route being used to dynamically generate menu items

Am incredibly stumped by this issue and have spent half a day troubleshooting it.

I am trying to dynamically generate menu items based on the directory structure in a Next.JS application. It works locally but not when building on netlify. I have tried changing the cases of all the files that are not being returned by the api so I don’t think it is that but who knows.

Two functions being used to generate the menuItems object.

getPages.js:

// scripts/getPages.js
import path from 'path';
import fs from 'fs';

export function getPages(dir, filelist = []) {
    const files = fs.readdirSync(dir);
    console.log("dir", dir)
    console.log("files", files)

    files.forEach(function (file) {
        console.log("file", file)
        if (fs.statSync(path.join(dir, file)).isDirectory()) {
            filelist = getPages(path.join(dir, file), filelist);
        }
        else {
            filelist.push(path.join(dir, file));
        }
    });
    console.log("fileList", filelist)

    return filelist;
}

/api/menuItems.ts


import { getPages } from '../../scripts/getPages';

export default async function handler(req, res) {

    const pages = getPages('./pages').filter(page => !page.includes('api') && !page.includes('_app') && !page.includes('_document'))
        .map(page => page.replace(/pages/, '')
            .replace(/\.tsx$/, '')
            .replace(/\.js$/, '')
            .replace(/\.ts$/, '')
            .replace(/index$/g, ''));

            
    console.log("pages after", pages);

    const menuItems = {};

    for (const page of pages) {
        const parts = page.split(/\\|\//).filter(part => part !== '');
        let currentLevel = menuItems;

        for (let i = 0; i < parts.length; i++) {
            let part = parts[i];

            // Replace underscores with spaces
            part = part.replace(/_/g, ' ');

            // Capitalize the first letter of the part if it's not already capitalized
            if (part[0] !== part[0].toUpperCase()) {
                part = part[0].toUpperCase() + part.slice(1);
            }

            if (!currentLevel[part]) {
                currentLevel[part] = {
                    href: `/${parts.slice(0, i + 1).join('/')}`,
                    text: part,
                    subItems: {}
                };
            }

            currentLevel = currentLevel[part].subItems;
        }
    }
    console.log("menuItems", menuItems);
    res.status(200).json(menuItems);
}

On my local environment I get this object at the /api/menuItems path:

{
   "Hoa":{
      "href":"/hoa",
      "text":"Hoa",
      "subItems":{
         "About":{
            "href":"/hoa/about",
            "text":"About",
            "subItems":{
               
            }
         },
         "Calendar":{
            "href":"/hoa/calendar",
            "text":"Calendar",
            "subItems":{
               
            }
         },
         "Documents":{
            "href":"/hoa/documents",
            "text":"Documents",
            "subItems":{
               
            }
         }
      }
   },
   "Map":{
      "href":"/map",
      "text":"Map",
      "subItems":{
         
      }
   },
   "PageList":{
      "href":"/PageList",
      "text":"PageList",
      "subItems":{
         
      }
   },
   "Resources":{
      "href":"/resources",
      "text":"Resources",
      "subItems":{
         "FAQ":{
            "href":"/resources/FAQ",
            "text":"FAQ",
            "subItems":{
               
            }
         },
         "Utilities and links":{
            "href":"/resources/utilities_and_links",
            "text":"Utilities and links",
            "subItems":{
               
            }
         }
      }
   }
}

You can see the truncated object being returned here: https://magenta-palmier-138a18.netlify.app/api/menuItems

{
   "Resources":{
      "href":"/resources",
      "text":"Resources",
      "subItems":{
         "FAQ":{
            "href":"/resources/FAQ",
            "text":"FAQ",
            "subItems":{
               
            }
         },
         "Utilities and links":{
            "href":"/resources/utilities_and_links",
            "text":"Utilities and links",
            "subItems":{
               
            }
         }
      }
   }
}

I have mainly been looking at the missing “hoa” folder that is not being returned in the menuItems object.

What has me confused is the console.logs I added all throughout both functions above does in fact show that the files/folders are being detected during the netlify build process but they are simply just not being returned by the API response.

Snippet of some netlify build logs here:

7:15:17 PM: Netlify Build                                                 
7:15:17 PM: ────────────────────────────────────────────────────────────────
7:15:17 PM: ​
7:15:17 PM: ❯ Version
7:15:17 PM:   @netlify/build 29.12.1
7:15:17 PM: ​
7:15:17 PM: ❯ Flags
7:15:17 PM:   baseRelDir: true
7:15:17 PM:   buildId: 6498f494186bea0008009ea9
7:15:17 PM:   config: /opt/build/repo/netlify.toml
7:15:17 PM:   deployId: 6498f494186bea0008009eab
7:15:17 PM:   tracing:
7:15:17 PM:     enabled: 'false'
7:15:17 PM:     host: 10.65.24.243
7:15:17 PM:     parentSpanId: 11e1cc006633ebf1
7:15:17 PM:     traceFlags: '01'
7:15:17 PM:     traceId: 153a3ad95451be201043638fac6906fa
7:15:17 PM: ​
7:15:17 PM: ❯ Current directory
7:15:17 PM:   /opt/build/repo
7:15:17 PM: ​
7:15:17 PM: ❯ Config file
7:15:17 PM:   /opt/build/repo/netlify.toml
7:15:17 PM: ​
7:15:17 PM: ❯ Context
7:15:17 PM:   production
7:15:17 PM: ​
7:15:17 PM: ❯ Using Next.js Runtime - v4.38.1
7:15:18 PM: ​
7:15:18 PM: @netlify/plugin-nextjs (onPreBuild event)                     
7:15:18 PM: ────────────────────────────────────────────────────────────────
7:15:18 PM: ​
7:15:18 PM: Next.js cache restored.
7:15:18 PM: Netlify configuration property build.environment.NEXT_PRIVATE_TARGET value changed.
7:15:18 PM: ​
7:15:18 PM: (@netlify/plugin-nextjs onPreBuild completed in 134ms)
7:15:18 PM: ​
7:15:18 PM: build.command from netlify.toml                               
7:15:18 PM: ────────────────────────────────────────────────────────────────
7:15:18 PM: ​
7:15:18 PM: $ next build
7:15:18 PM: info  - Loaded env from /opt/build/repo/.env.production
7:15:18 PM: warn  - Detected next.config.js, no exported configuration found. https://nextjs.org/docs/messages/empty-configuration
7:15:18 PM: info  - Linting and checking validity of types...
7:15:23 PM: info  - Creating an optimized production build...
7:15:49 PM: info  - Compiled successfully
7:15:49 PM: info  - Collecting page data...
7:16:04 PM: info  - Generating static pages (0/12)
7:16:19 PM: dir ./pages
7:16:19 PM: files [
7:16:19 PM:   'PageList.tsx',
7:16:19 PM:   '_app.tsx',
7:16:19 PM:   '_document.js',
7:16:19 PM:   'api',
7:16:19 PM:   'hoa',
7:16:19 PM:   'index.tsx',
7:16:19 PM:   'map.tsx',
7:16:19 PM:   'resources'
7:16:19 PM: ]
7:16:19 PM: file PageList.tsx
7:16:19 PM: file _app.tsx
7:16:19 PM: file _document.js
7:16:19 PM: file api
7:16:19 PM: dir pages/api
7:16:19 PM: files [ 'auth', 'hello.js', 'menuItems.ts', 'pages.ts' ]
7:16:19 PM: file auth
7:16:19 PM: dir pages/api/auth
7:16:19 PM: files [ 'auth.tsx' ]
7:16:19 PM: file auth.tsx
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx'
7:16:19 PM: ]
7:16:19 PM: file hello.js
7:16:19 PM: file menuItems.ts
7:16:19 PM: file pages.ts
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts'
7:16:19 PM: ]
7:16:19 PM: file hoa
7:16:19 PM: dir pages/hoa
7:16:19 PM: files [ 'about.tsx', 'calendar', 'documents', 'index.tsx' ]
7:16:19 PM: file about.tsx
7:16:19 PM: file calendar
7:16:19 PM: dir pages/hoa/calendar
7:16:19 PM: files [ 'index.tsx' ]
7:16:19 PM: file index.tsx
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx'
7:16:19 PM: ]
7:16:19 PM: file documents
7:16:19 PM: dir pages/hoa/documents
7:16:19 PM: files [ 'index.tsx' ]
7:16:19 PM: file index.tsx
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx',
7:16:19 PM:   'pages/hoa/documents/index.tsx'
7:16:19 PM: ]
7:16:19 PM: file index.tsx
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx',
7:16:19 PM:   'pages/hoa/documents/index.tsx',
7:16:19 PM:   'pages/hoa/index.tsx'
7:16:19 PM: ]
7:16:19 PM: file index.tsx
7:16:19 PM: file map.tsx
7:16:19 PM: file resources
7:16:19 PM: dir pages/resources
7:16:19 PM: files [ 'FAQ.tsx', 'index.tsx', 'utilities_and_links.tsx' ]
7:16:19 PM: file FAQ.tsx
7:16:19 PM: file index.tsx
7:16:19 PM: file utilities_and_links.tsx
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx',
7:16:19 PM:   'pages/hoa/documents/index.tsx',
7:16:19 PM:   'pages/hoa/index.tsx',
7:16:19 PM:   'pages/index.tsx',
7:16:19 PM:   'pages/map.tsx',
7:16:19 PM:   'pages/resources/FAQ.tsx',
7:16:19 PM:   'pages/resources/index.tsx',
7:16:19 PM:   'pages/resources/utilities_and_links.tsx'
7:16:19 PM: ]
7:16:19 PM: fileList [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx',
7:16:19 PM:   'pages/hoa/documents/index.tsx',
7:16:19 PM:   'pages/hoa/index.tsx',
7:16:19 PM:   'pages/index.tsx',
7:16:19 PM:   'pages/map.tsx',
7:16:19 PM:   'pages/resources/FAQ.tsx',
7:16:19 PM:   'pages/resources/index.tsx',
7:16:19 PM:   'pages/resources/utilities_and_links.tsx'
7:16:19 PM: ]
7:16:19 PM: [
7:16:19 PM:   'pages/PageList.tsx',
7:16:19 PM:   'pages/_app.tsx',
7:16:19 PM:   'pages/_document.js',
7:16:19 PM:   'pages/api/auth/auth.tsx',
7:16:19 PM:   'pages/api/hello.js',
7:16:19 PM:   'pages/api/menuItems.ts',
7:16:19 PM:   'pages/api/pages.ts',
7:16:19 PM:   'pages/hoa/about.tsx',
7:16:19 PM:   'pages/hoa/calendar/index.tsx',
7:16:19 PM:   'pages/hoa/documents/index.tsx',
7:16:19 PM:   'pages/hoa/index.tsx',
7:16:19 PM:   'pages/index.tsx',
7:16:19 PM:   'pages/map.tsx',
7:16:19 PM:   'pages/resources/FAQ.tsx',
7:16:19 PM:   'pages/resources/index.tsx',
7:16:19 PM:   'pages/resources/utilities_and_links.tsx'
7:16:19 PM: ]
7:16:19 PM: info  - Generating static pages (3/12)
7:16:19 PM: info  - Generating static pages (6/12)
7:16:20 PM: info  - Generating static pages (9/12)
7:16:20 PM: info  - Generating static pages (12/12)
7:16:20 PM: info  - Finalizing page optimization...

Further confusing me is that the pages are in fact available to be accessed:

etc. they just don’t show up in the menuItems object.

At the point where I need a second pair of eyes on this as I am genuinely baffled by this.

Happy to supply any other info needed.

Hi, are you still experiencing this issue? I see your most recent deploy was successful.

Hi, yes still experiencing the issue. To be clear it does not make the build fail. There just is an issue with the node fs.readdirSync function actually being able to access all of the files in the directory. The logs show the files and directories are there but they aren’t being returned in the menuItems object.

Have you included the files in your functions: How to Include Files in Netlify Serverless Functions?

I did not know about this but reading that article you linked helped me resolve the issue.

Adding these lines into the netlify.toml file fixed it:

# Include all files in all functions
[functions]
  included_files = ["pages/**"]

Now seeing all relevant pages here:
https://magenta-palmier-138a18.netlify.app/api/menuItems

Thank you for pointing me in the right direction!!