santifer/career-ops · error · Error
joinup: ${entry.name} failed to parse __NEXT_DATA__ — ${err.
Error message
joinup: ${entry.name} failed to parse __NEXT_DATA__ — ${err.message} What it means
Thrown by joinup.fetch() when the __NEXT_DATA__ script tag was found (regex matched) but JSON.parse() of its contents threw, OR the parsed object's deep path (props.pageProps.serverState.initialResults.jobs.results) did not yield a usable hits array. The error message chains the underlying parse/access error so the root cause is visible.
Source
Thrown at providers/joinup.mjs:59
try { host = new URL(entry.careers_url || '').hostname; } catch { return null; }
return /(^|\.)joinup\.ch$/i.test(host) ? { url: BROWSE_URL } : null;
},
async fetch(entry, ctx) {
// redirect:'error' — BROWSE_URL is pinned to joinup.ch (https); a 3xx must
// not be followed to a private/metadata IP (matches every other provider).
const html = await ctx.fetchText(BROWSE_URL, { redirect: 'error' });
const m = html.match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
// Fail closed: a missing/unparseable __NEXT_DATA__ is a scraper break, not an
// empty board — throw so the scan logs it instead of silently reporting zero.
if (!m) throw new Error(`joinup: ${entry.name} page is missing __NEXT_DATA__ (structure changed?)`);
let hits = [];
try {
const data = JSON.parse(m[1]);
const ir = data?.props?.pageProps?.serverState?.initialResults?.jobs?.results;
hits = Array.isArray(ir) && ir[0]?.hits ? ir[0].hits : [];
} catch (err) {
throw new Error(`joinup: ${entry.name} failed to parse __NEXT_DATA__ — ${err.message}`);
}
return hits
.filter(h => h && h.slug && (h.title || h.headline))
.map(h => ({
title: h.title || h.headline || '',
url: `https://joinup.ch/job/${h.slug}`,
company: h.startup || entry.name || '',
location: typeof h.location === 'string' ? h.location
: (h.location?.name || h.location?.city || ''),
postedAt: toEpochMs(h.created),
}));
},
};
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Capture the raw __NEXT_DATA__ content for the failing entry and validate it with a JSON linter.
- If the structure changed, update the data-access path (props.pageProps.serverState.initialResults.jobs.results) to the new nesting.
- If the JSON is genuinely truncated, investigate whether a content-encoding or transfer issue is cutting the response.
Defensive patterns
Strategy: try-catch
Validate before calling
function joinupPayloadOk(html) {
const m = html.match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
if (!m) return false;
try {
const ir = JSON.parse(m[1])?.props?.pageProps?.serverState?.initialResults?.jobs?.results;
return Array.isArray(ir) && (!ir[0] || Array.isArray(ir[0].hits));
} catch { return false; }
} Type guard
/** @param {any} data @returns {boolean} */
function joinupDataHasHits(data) {
const ir = data?.props?.pageProps?.serverState?.initialResults?.jobs?.results;
return Array.isArray(ir) && (!ir[0] || Array.isArray(ir[0].hits));
} Try / catch
try {
jobs = await provider.fetch(entry, ctx);
} catch (err) {
if (/failed to parse __NEXT_DATA__/.test(err.message)) {
console.error(`joinup ${entry.name}: JSON parse failed — ${err.message}`);
}
throw err;
} Prevention
- Validate the nested serverState path exists before depending on it; log the actual keys on failure.
- Keep a fixture payload in tests so a joinup.ch nesting change breaks CI, not production scans.
- Distinguish JSON.parse failures (truncated/encoded) from access failures (renamed keys) in logs.
When it happens
Trigger: The __NEXT_DATA__ script contains malformed JSON (truncated, encoded, or injected with non-JSON content); the JSON parses but the nested serverState/initialResults structure differs from what the provider expects (keys renamed or moved); results is present but the inner hits array is absent so the fallback yields [].
Common situations: joinup.ch changed its server-state nesting (renamed initialResults or restructured results); a partial page render truncated the script content; a different page type was served that embeds a valid but differently-shaped __NEXT_DATA__.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- joinup: ${entry.name} page is missing __NEXT_DATA__ (structu
- join: __NEXT_DATA__ not found or unexpected structure
- API error: ${json.errorMsg || json.errorCode || 'success=fal
- jobvite: could not find companyEId on ${boardUrl} for ${entr
- justjoin: unexpected API response — expected { data: [...] }
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/1e9ac712c7b2100e.
Report an issue: GitHub.