{"record":{"id":"ef333efe2d3b9511","repo":"DIYgod/RSSHub","slug":"unhandled-node-type-node-nodetype","errorCode":null,"errorMessage":"Unhandled node type: ${node.nodeType}","messagePattern":"Unhandled node type: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/netflix/newsroom.ts","lineNumber":209,"sourceCode":"            }\n            return `<a href=\"${embedLink}\" target=\"_blank\" rel=\"noopener noreferrer\">${node.data.title ?? embedLink}</a>`;\n        }\n\n        case 'list-item': {\n            const innerHTML = node.content?.map((c) => render(c)).join('') || '';\n            return `<li>${innerHTML}</li>`;\n        }\n        case 'unordered-list': {\n            const itemsHTML = node.content?.map((c) => render(c)).join('') || '';\n            return `<ul>${itemsHTML}</ul>`;\n        }\n        case 'ordered-list': {\n            const itemsHTML = node.content?.map((c) => render(c)).join('') || '';\n            return `<ol>${itemsHTML}</ol>`;\n        }\n\n        default:\n            throw new Error(`Unhandled node type: ${node.nodeType}`);\n    }\n};\n\nasync function handler(ctx) {\n    const { category = 'all', region = 'en' } = ctx.req.param();\n\n    const baseUrl = 'https://about.netflix.com';\n\n    const response = await ofetch(`${baseUrl}/api/data/articles`, {\n        query: {\n            language: region,\n            category: categories[category].id,\n        },\n    });\n\n    const list = [...response.entities.region.regionArticles, ...response.entities.global.globalArticles].map((i): DataItem & { slug: string } => ({\n        title: i.title,\n        link: `${baseUrl}/${region}/news/${i.slug}`,","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/netflix/newsroom.ts#L191-L227","documentation":"Thrown by the Contentful rich-text renderer in the Netflix newsroom route. The render() function switches on node.nodeType and only handles a fixed set (text, hyperlink, embedded-asset-block, embedded-entry-block, embedded-entry-inline, list-item, unordered-list, ordered-list). Any Contentful node whose nodeType is not one of those cases falls through to default and throws, surfacing the unhandled type name. It is a defensive guard meant to expose schema gaps, not a normal runtime condition.","triggerScenarios":"A fetched Netflix article body contains a Contentful node type the switch does not recognize — e.g. 'paragraph', 'heading-1', 'heading-2', 'blockquote', 'hr', 'table', 'table-cell', 'entry-hyperlink', or 'asset-hyperlink'. Also triggered if Contentful ships a new block type or editors insert a block (table, divider, embed) that was previously absent from the feed.","commonSituations":"Contentful editors add a new content block (heading, quote, divider, table) to a newsroom post; Contentful bumps the rich-text schema and adds nodeTypes; the route was written against a limited sample of articles and a new article exercises an unhandled node. The message bubbles up as a 500 to RSSHub consumers.","solutions":["Inspect the thrown node.nodeType value from the error message and add a dedicated case to the switch in lib/routes/netflix/newsroom.ts that renders that node (e.g. case 'paragraph' / 'heading-1' / 'blockquote').","If the node is non-critical, add a case that returns its inner HTML or empty string so the feed degrades gracefully instead of 500-ing.","As a stopgap, change the default branch to log the unhandled type via logger and return '' (or node.content mapped through render) so unknown nodes never break the whole feed.","Report the new nodeType to the route maintainers so the case ships upstream."],"exampleFix":"// before\ndefault:\n    throw new Error(`Unhandled node type: ${node.nodeType}`);\n\n// after\ndefault:\n    logger.warn(`Unhandled node type: ${node.nodeType}`);\n    return node.content?.map((c) => render(c)).join('') ?? '';","handlingStrategy":"type-guard","validationCode":"// Before rendering, confirm every node is one the renderer supports.\nconst HANDLED = new Set(['text','hyperlink','embedded-asset-block','embedded-entry-block','embedded-entry-inline','list-item','unordered-list','ordered-list']);\nfunction assertRenderable(nodes) {\n  const unknown = [];\n  for (const n of nodes) {\n    if (!HANDLED.has(n.nodeType)) unknown.push(n.nodeType);\n    if (n.content) assertRenderable(n.content);\n  }\n  if (unknown.length) logger.warn('Unhandled node types seen:', [...new Set(unknown)]);\n}","typeGuard":"const isHandledNode = (n): n is { nodeType: string; content?: any[]; data?: any } =>\n  typeof n?.nodeType === 'string' && HANDLED.has(n.nodeType);","tryCatchPattern":"// Wrap the top-level render call so one bad node does not 500 the whole feed.\nfunction safeRender(node) {\n  try { return render(node); }\n  catch (e) {\n    logger.warn('render failed for node type', node?.nodeType, e);\n    return '';\n  }\n}","preventionTips":["Never let the Contentful renderer throw from default — log and return '' so unknown nodes degrade gracefully.","Add unit tests against a sample of real article JSON to catch new nodeTypes before they hit production.","Pin/monitor the Contentful schema; when editors add block types, extend the switch in the same change."],"tags":["contentful","rich-text","switch-statement","parsing","netflix"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}