17

I don't understand these errors when I export as production npm run build , but when I test npm run dev it works just fine. I use getStaticProps and getStaticPath fetch from an API route.

First when I npm run build

FetchError: invalid json response body at https://main-website-next.vercel.app/api/products reason: Unexpected token T in JSON at position
0
    at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:272:32
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\product\[slug].js:1324:18)
    at async buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:80)
    at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:612
    at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
  type: 'invalid-json'
}

\pages\product\[slug]

import { assetPrefix } from '../../next.config'

export default function Page(){...}

export const getStaticProps = async ({ params: { slug }, locale }) => {
  const res = await fetch(`${assetPrefix}/api/products/${slug}`)
  const result = await res.json()
  const data = result.filter(item => item.locale === locale)[0]
  const { title, keywords, description } = data
  return {
    props: {
      data,
      description,
      keywords, 
      title
    }
  }
}

export const getStaticPaths = async () => {
  const res = await fetch(`${assetPrefix}/api/products`)
  const result = await res.json()
  const paths = result.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
  return {
    fallback: true,
    paths,
  }
}

next.config.js

const isProd = process.env.NODE_ENV === 'production'

module.exports = {
  assetPrefix: isProd ? 'https://main-website-next.vercel.app' : 'http://localhost:3000',
  i18n: {
    localeDetection: false,
    locales: ['en', 'th'],
    defaultLocale: 'en',
  }
}

API routes

// pages/api/products/index.js
import data from '../../../data/products'
export default (req, res) => {
  res.status(200).json(data)
}

// pages/api/products/[slug].js
import db from '../../../data/products'
export default ({ query: { slug } }, res) => {
  const data = db.filter(item => item.slug === slug)
  if (data.length > 0) {
    res.status(200).json(data)
  } else {
    res.status(404).json({ message: `${slug} not found` })
  }
}

// ../../../data/products (data source)
module.exports = [
  { locale: "en", slug: "google-sheets-combine-your-cashflow",
    title: "Combine your cashflow",
    keywords: ["Google Sheets","accounting"],
    description: "...",
  },
    ...
]

Second when I remove the production domain, I run npm run build but still get the error like

TypeError: Only absolute URLs are supported
    at getNodeRequestOptions (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1305:9)
    at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1410:19
    at new Promise (<anonymous>)
    at fetch (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1407:9)
    at getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\[slug].js:938:21)
    at buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:86)
    at D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:618
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
  type: 'TypeError'
}

My next.config.js after remove

const isProd = process.env.NODE_ENV === 'production'

module.exports = {      //remove
  assetPrefix: isProd ? '' : 'http://localhost:3000',
  i18n: {
    localeDetection: false,
    locales: ['en', 'th'],
    defaultLocale: 'en',
  }
}

My package.json when I npm run build script

{
  "name": "main-website-next",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build && next export",
    "start": "next start"
  },
  "dependencies": {
    "next": "10.0.6",
    "react": "17.0.1",
    "react-dom": "17.0.1"
  }
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
zummon
  • 906
  • 3
  • 9
  • 27
  • Can you share your API routes code? Rather than calling your API routes you should use the data fetching logic directly in `getStaticProps`/`getStaticPaths`. – juliomalves Feb 15 '21 at 09:01
  • 1
    thank you. I've added more info in the question above, if i understand correctly, if it's not what you looking for please tell me what you need. I'm really new to next js – zummon Feb 15 '21 at 09:29
  • @juliomalves use the data fetching logic directly `fetch('${assetPrefix}/api/products/${slug}')` change to `fetch('${assetPrefix}/data/products')` or `fetch(/next/server...)` right? But I don't know the correct one. Also I'm only fetching data inside its project – zummon Feb 15 '21 at 09:55

1 Answers1

23

You should not call an internal API route inside getStaticProps. Instead, you can safely use your API logic directly in getStaticProps/getStaticPaths. These only happen server-side so you can write server-side code directly.

As getStaticProps runs only on the server-side, it will never run on the client-side. It won’t even be included in the JS bundle for the browser, so you can write direct database queries without them being sent to browsers.

This means that instead of fetching an API route from getStaticProps (that itself fetches data from an external source), you can write the server-side code directly in getStaticProps.

Furthermore, your API routes are not available during build-time, as the server has not been started at that point.


Here's a small refactor of your code to address the issue.

// /pages/product/[slug]

import db from '../../../data/products'

// Remaining code..

export const getStaticProps = async ({ params: { slug }, locale }) => {
    const result = db.filter(item => item.slug === slug)
    const data = result.filter(item => item.locale === locale)[0]
    const { title, keywords, description } = data
    return {
        props: {
            data,
            description,
            keywords, 
            title
        }
    }
}

export const getStaticPaths = async () => {
    const paths = db.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
    return {
        fallback: true,
        paths,
    }
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • 1
    Thank you I now understand more, but however this time shows `Error occurred prerendering page "/en/product/[slug]"` - `TypeError: Cannot read property 'contents' of undefined` instead. but for test server is run okay – zummon Feb 15 '21 at 13:23
  • Make sure to have checks on your data to avoid accessing `undefined` props which trigger errors like that one. – juliomalves Feb 15 '21 at 13:26
  • 1
    Thank you a lot, this should have already fixed the error as the question I've asked. For the next error I'm facing is out of scope, also there something that I can fix myself but i don't see it yet *I thought just testing `npm run dev` work just fine, but it's apparently not the same when come to production build – zummon Feb 15 '21 at 15:45
  • @juliomalves how do I perform undefined checks ? – Sushilzzz Jan 09 '22 at 07:01
  • @Sushilzzz Check the object is not `undefined` before accessing one of its properties. – juliomalves Jan 09 '22 at 10:13
  • 1
    Just coming here after doing the NextJS started app and the explanation regarding "why you should not use API routes inside of getStatic*" was not enough, but your "API routes are not available during build-time, as the server has not been started at that point." is perfectly fine and makes total sense. – TimPietrusky Jan 02 '23 at 12:51
  • I get it, but not sure this is a solution for every case. I have api routes because I want other projects to use it (like mobile app), so I put DB queries in API code. It's obviously much easier to just use the same api in getStatic* than duplicating DB queries code there again (or splitting the API code into api and db queries, or splitting the nextjs project in two). I don't get this. Why not possible to just run `npm run dev` in another terminal – Mirko Vukušić Jan 23 '23 at 11:55
  • @MirkoVukušić You can extract the DB logic into separate functions and reuse them both in the API routes and in `getStatic*` functions. It all depends how you want to structure your code. – juliomalves Jan 23 '23 at 13:44
  • @juliomlaves yes I know, that's what I mentioned (in brackets) and what I do in larger projects. But many times I want to combine api code with db querying code, especially when those queries are simple and project not large. Because now with apiDir we have separate folder instead of pges files, separate Head, Layout, ... files. Now also separate api with db queries...well, it's a lot of boilerplate with same-named files. I don't like it a bit. – Mirko Vukušić Jan 25 '23 at 18:05
  • What's the point of developing your own `/api` folder then? – m3.b Feb 17 '23 at 04:42
  • @m3.b For scenarios where you need to make the requests from the client-side. – juliomalves Feb 17 '23 at 12:55
  • Could you give me an example? @juliomalves – m3.b Feb 17 '23 at 18:09
  • @m3.b For instance, if you need to call the API on a button click action by the user. – juliomalves Feb 17 '23 at 18:26
  • @m3.b If you're asking why use Next.js API routes if you already have an external API, then here's the answer: https://stackoverflow.com/a/69536148/1870780. – juliomalves Feb 17 '23 at 18:28
  • No I just wanted to use my internal API. Making request to my database making axios requests – m3.b Feb 17 '23 at 18:34