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.
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#
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.
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.
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:
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:
✓ 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:
$ 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 circleEither 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
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
NextAuth: Property 'session' does not exist on type '{}'
Reproduced on Next.js 12.3.4, 13.0.0 and 16.4.0: why the session prop broke in _app.tsx, the one-line generic that fixes it, and why the d.ts override only seems to work.
Fix "cookies() should be awaited" Error in Next.js 15
Next.js 15 broke synchronous `cookies().get()`. Every server-side call must now `await cookies()` first. Here's the precise migration — App Router pages, route handlers, Server Actions, and Supabase SSR — plus the codemod that fixes 90% of call sites automatically.
Fix 'Hydration failed' in Next.js: 8 Root Causes (2026)
Hydration mismatch errors breaking your Next.js app? Learn the root causes and 8 proven fixes to eliminate these errors permanently.


