Next.js

Fix: "[object Date]" Cannot Be Serialized as JSON

A Date prop cannot go straight from getStaticProps to the page — convert it first, or the page will not build.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

getStaticProps or getServerSideProps returned a JavaScript Date object as a prop. Next.js's Pages Router checks that every prop is a plain scalar, object or array — nothing else — before it ever reaches the page component, and a Date fails that check, so the build (or the request, in dev) fails with Reason: \object` ("[object Date]") cannot be serialized as JSON. Convert the date to a string or a number before returning it: date.toISOString(), a Unix timestamp, or wrap the whole props object in JSON.parse(JSON.stringify(props))`.

The error#

text
Error serializing `.newsLetters[0].createdAt` returned from `getStaticProps` in "/".
Reason: `object` ("[object Date]") cannot be serialized as JSON. Please only return JSON serializable data types.

This is the exact error from a Stack Overflow question with 27 votes and 22,650 views: a Prisma query inside getStaticProps returns rows whose createdAt column is a Date object, and passing that array straight through as props fails. The path in the first line (.newsLetters[0].createdAt) points at the exact field — the array index and the property name — so on a page with several fields you can go straight to the one the build actually flagged, instead of guessing which of the returned columns is the Date.

Why it happens#

Before getStaticProps or getServerSideProps hand their return value to the page, Next.js walks it with its own isSerializableProps check (lib/is-serializable-props.ts, tag v15.5.23). That function recurses into plain objects and arrays, but anything else — a Date, a Map, a class instance, a function — falls through to one final throw, built from typeof value and, for an object, Object.prototype.toString.call(value). For a Date that call returns the literal string "[object Date]", which is exactly what lands in the error message: the error is not a generic complaint, it is typeof and Object.prototype.toString printed back at you.

A Next.js core team member explained the intent on the GitHub issue the accepted answer links to: props are not run through a generic serializer on the server for performance reasons, so the check instead requires scalar values that will match exactly once they reach the client. Reading server/render.tsx at the same tag shows where that check actually runs: (dev || isBuildTimeSSG) && !isSerializableProps(...), once for getStaticProps and once for getServerSideProps. getServerSideProps never runs during a build, so for it the check only ever fires in next dev — which matches the 2020 comment. getStaticProps does run during a build (isBuildTimeSSG is true while next build exports the static HTML), which is exactly the run captured below: a page with one Date field fails next build with the error above, and passes once that one field is converted — no other line in the page changed. The check is also absent from the compiled production-serving bundle, so it never runs again once the built app is actually serving requests.

This check, and the two data-fetching functions it gates, belong to the Pages Router only. JSON.stringify itself handles a Date without complaint (it calls Date.prototype.toJSON(), which returns an ISO string) — the error here comes from this extra, stricter check Next.js runs first, not from JSON's own rules.

Fix#

1. Convert the field at the source, before it reaches props.

javascript
export const getStaticProps = async () => {
  const prisma = new PrismaClient();
  const newsLetters = await prisma.newsLetters.findMany();
 
  const serialized = newsLetters.map((row) => ({
    ...row,
    createdAt: row.createdAt.toISOString(),
  }));
 
  return {
    props: {
      newsLetters: serialized,
    },
  };
};

Use toISOString() when the page needs to format or sort the date later (a string sorts and parses predictably); use Math.floor(row.createdAt.getTime() / 1000) for a Unix timestamp in seconds if the page only needs to display a relative time — multiply it back by 1000 before passing it to new Date(...) on the client, since the Date constructor expects milliseconds, not seconds. Spreading the row (...row) does not make this fix safe on its own: any other Date column on the same row (a typical updatedAt alongside createdAt) is copied through unconverted and fails the same check on the next field. Convert every Date column the row actually has, or use fix 2 below instead of listing them one by one.

2. Or round-trip the whole props object through JSON — one line, no per-field edits.

javascript
export const getStaticProps = async () => {
  const prisma = new PrismaClient();
  const newsLetters = await prisma.newsLetters.findMany();
 
  return {
    props: {
      newsLetters: JSON.parse(JSON.stringify(newsLetters)),
    },
  };
};

This is the fix from the Stack Overflow answer with 30 votes, and the one reproduced above: it turns every Date into an ISO string, drops any undefined field, and throws if the data contains a value JSON genuinely cannot represent (a BigInt, a circular reference). It is a shortcut, not a type-safe one — check what it does to deeply nested dates on your own payload before using it on something larger than a few fields.

3. TypeScript: do not reassign the field's type.

If newsLetters is typed with createdAt: Date, this will not compile:

typescript
newsLetter.createdAt = newsLetter.createdAt.toString();
// Error: Type 'string' is not assignable to type 'Date'.

Build a new object (or a new type) instead of mutating the typed one — the map() in fix 1, or the JSON.parse(JSON.stringify(...)) in fix 2, both return a new value rather than reassigning a field typed as Date.

Verify the fix#

Run next build (or reload the page in next dev) after the change. The build should reach "Generating static pages" and finish without the prerender-error message; in dev, the page should render instead of showing the 500 overlay. Reproducing fix 2 on Next.js 15.5.23 turns the earlier failing build into this instead:

text
✓ Compiled successfully in 2.6s
   Collecting page data ...
   Generating static pages (0/3) ...
 ✓ Generating static pages (3/3)
   Finalizing page optimization ...
   Collecting build traces ...

No other input changed between the two runs — same page, same dependencies, one line edited. If the page uses the date for display, check the rendered output: an ISO string like 2026-10-06T12:00:00.000Z is not very readable on its own — format it client-side with Intl.DateTimeFormat or a date library rather than printing the raw string.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

undefined, BigInt and circular references are not the same error#

The check that rejects a Date also rejects a handful of other values, but not with the same message. Reading the same source function shows undefined gets its own line before the Date case is ever reached: `undefined` cannot be serialized as JSON. Please use `null` or omit this value. — if a prop is sometimes undefined (an optional database column, a conditional field), that is the message to search for, not this one; the fix is the same shape (null instead of undefined, or drop the key), but it is a different error string to grep your logs for.

BigInt, a Map or a Set are not caught by Next's own check with a tailored message either — they fall into the same final throw as Date, so they would also read `object` ("[object Map]") or `bigint` cannot be serialized. A circular reference does get its own message from the same function, one step earlier, before the typeof/toString fallback is ever reached: Circular references cannot be expressed in JSON (references: `...`). (lib/is-serializable-props.ts, tag v15.5.23). If you use fix 2 (JSON.parse(JSON.stringify(...))) instead, JSON.stringify throws its own, unrelated errors for the BigInt and circular cases before Next's check ever runs:

text
$ node -e "JSON.stringify({a:1n})"
Do not know how to serialize a BigInt
 
$ node -e "const o={}; o.self=o; JSON.stringify(o)"
Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle

Either way, the fix is the same principle as for Date: convert the value to a plain scalar (a string, a number, null) before it reaches props.

If a Prisma query returns the Date object before this point#

prisma.newsLetters.findMany() is only the data source in this example; the same fix applies regardless of what produced the Date — a raw pg query, or a new Date() added manually inside getStaticProps. A Supabase client call is a different case: supabase-js talks to PostgREST over HTTP and returns JSON, so a timestamp column already arrives as an ISO string, never as a Date instance — nothing to convert there. If the page instead throws supabaseUrl is required before it ever gets this far, that is a separate, earlier configuration error — fix the client setup first.

This error, and the getStaticProps function that triggers it, both belong to the Pages Router model. A page migrated to the App Router with cacheComponents enabled hits a different family of errors instead — reading cookies() or headers() inside cached code, or a missing cacheLife — not this serialization check, because the App Router no longer passes props through it.

A build that fails only on this error and nothing else is still worth a second check before shipping: a green build does not guarantee a working production deploy if the underlying data access (an env var, an admin client such as the one described in Supabase's service_role key guide) was only ever tested locally.

Passing complex, deeply-nested data (dates buried several levels deep, or values the two fixes above cannot safely represent) is a genuinely harder problem than a single top-level Date field. For that case, a dedicated serialization library such as superjson — which encodes and decodes Date, Map, Set, and other non-JSON types explicitly, rather than relying on a lossy JSON.stringify round trip — is worth adopting on the Pages Router. getStaticProps must still return { props: { ... } } — any other top-level key is rejected before serialization is even checked (server/render.tsx, tag v15.5.23) — so nest superjson.serialize()'s own { json, meta } result inside props instead of returning it as the props object itself: return { props: superjson.serialize(data) }, then call superjson.deserialize(props) on the client side of the page component to get the original value back, Date instances included. Older answers also suggest a next-superjson-plugin SWC plugin to automate that call; checking the npm registry on 6 October 2026 shows its latest release (0.6.3) was published 3 March 2024 with peerDependencies.next: "^13.0 || ^14.0" — it predates Next.js 15, so use the plain superjson function calls instead of that plugin on a current Next.js version.

Further reading: NextAuth: Property 'session' does not exist on type ''.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote