DIYgod/RSSHub · error · Error

Unhandled node type: ${node.nodeType}

Error message

Unhandled node type: ${node.nodeType}

What it means

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.

Source

Thrown at lib/routes/netflix/newsroom.ts:209

            }
            return `<a href="${embedLink}" target="_blank" rel="noopener noreferrer">${node.data.title ?? embedLink}</a>`;
        }

        case 'list-item': {
            const innerHTML = node.content?.map((c) => render(c)).join('') || '';
            return `<li>${innerHTML}</li>`;
        }
        case 'unordered-list': {
            const itemsHTML = node.content?.map((c) => render(c)).join('') || '';
            return `<ul>${itemsHTML}</ul>`;
        }
        case 'ordered-list': {
            const itemsHTML = node.content?.map((c) => render(c)).join('') || '';
            return `<ol>${itemsHTML}</ol>`;
        }

        default:
            throw new Error(`Unhandled node type: ${node.nodeType}`);
    }
};

async function handler(ctx) {
    const { category = 'all', region = 'en' } = ctx.req.param();

    const baseUrl = 'https://about.netflix.com';

    const response = await ofetch(`${baseUrl}/api/data/articles`, {
        query: {
            language: region,
            category: categories[category].id,
        },
    });

    const list = [...response.entities.region.regionArticles, ...response.entities.global.globalArticles].map((i): DataItem & { slug: string } => ({
        title: i.title,
        link: `${baseUrl}/${region}/news/${i.slug}`,

View on GitHub (pinned to bed535e087)

Solutions

  1. 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').
  2. 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.
  3. 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.
  4. Report the new nodeType to the route maintainers so the case ships upstream.

Example fix

// before
default:
    throw new Error(`Unhandled node type: ${node.nodeType}`);

// after
default:
    logger.warn(`Unhandled node type: ${node.nodeType}`);
    return node.content?.map((c) => render(c)).join('') ?? '';
Defensive patterns

Strategy: type-guard

Validate before calling

// Before rendering, confirm every node is one the renderer supports.
const HANDLED = new Set(['text','hyperlink','embedded-asset-block','embedded-entry-block','embedded-entry-inline','list-item','unordered-list','ordered-list']);
function assertRenderable(nodes) {
  const unknown = [];
  for (const n of nodes) {
    if (!HANDLED.has(n.nodeType)) unknown.push(n.nodeType);
    if (n.content) assertRenderable(n.content);
  }
  if (unknown.length) logger.warn('Unhandled node types seen:', [...new Set(unknown)]);
}

Type guard

const isHandledNode = (n): n is { nodeType: string; content?: any[]; data?: any } =>
  typeof n?.nodeType === 'string' && HANDLED.has(n.nodeType);

Try / catch

// Wrap the top-level render call so one bad node does not 500 the whole feed.
function safeRender(node) {
  try { return render(node); }
  catch (e) {
    logger.warn('render failed for node type', node?.nodeType, e);
    return '';
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/ef333efe2d3b9511. Report an issue: GitHub.