toeverything/AFFiNE · error

Invalid link preview response

Error message

Invalid link preview response

What it means

After streaming the body, readLinkPreviewResponse JSON-parses it and validates against LinkPreviewResponseSchema via parseLinkPreviewResponse, which returns undefined on schema failure. If parsing or validation fails, 'Invalid link preview response' is thrown. The schema requires at minimum a valid http/https url without credentials; all other fields are optional or lenient.

Solutions

  1. Log the raw response body and validate it against the schema requirements (must be JSON with a valid http/https 'url')
  2. Confirm the preview backend returns the documented schema and hasn't changed its API shape
  3. Check for proxies/CDNs intercepting and returning HTML (login pages, error pages) with 200 status
  4. Catch the error and fall back to rendering a plain link without preview data

Example fix

// before
const data = await readLinkPreviewResponse(res);
// after
try {
  const data = await readLinkPreviewResponse(res);
} catch (e) {
  if (e.message === 'Invalid link preview response') {
    const raw = await res.clone().text(); // inspect what the server actually sent
    console.warn('Unexpected preview payload:', raw.slice(0, 200));
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// validate after fetch, before parsing expectations
const clone = response.clone();
const text = await clone.text();
let parsed: unknown;
try {
  parsed = JSON.parse(text);
} catch {
  throw new Error('Preview response is not JSON');
}
if (
  typeof parsed !== 'object' || parsed === null ||
  !('url' in parsed) || typeof (parsed as {url: unknown}).url !== 'string' ||
  !/^https?:\/\/[^@]+@/.test((parsed as {url: string}).url) === false
) {
  throw new Error('Preview response missing valid http(s) url');
}

Type guard

function isLinkPreviewResponse(v: unknown): v is { url: string; [k: string]: unknown } {
  if (typeof v !== 'object' || v === null) return false;
  const url = (v as { url?: unknown }).url;
  if (typeof url !== 'string') return false;
  try {
    const u = new URL(url);
    return ['http:', 'https:'].includes(u.protocol) && !u.username && !u.password;
  } catch {
    return false;
  }
}

Try / catch

try {
  const data = await readLinkPreviewResponse(response);
} catch (e) {
  if (e.message === 'Invalid link preview response') {
    // log body snippet and render plain link fallback
  } else throw e;
}

Prevention

When it happens

Trigger: The response body is not valid JSON (HTML error page, truncated body) so JSON.parse throws, or the parsed object fails the zod schema — most commonly missing or invalid 'url' field, a non-http(s) URL, or a URL containing username/password credentials.

Common situations: The preview backend returned an HTML error page with a 200 status; the backend API changed shape (version drift); the response was truncated by an intermediary proxy; the 'url' field uses ftp: or includes embedded credentials.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-09-15). Data as JSON: /api/errors/fa0e59b679577900. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/affine/shared/src/services/link-preview-service/response.ts:112

  let bytes = 0;
  let json = '';
  try {
    for (;;) {
      const { done, value } = await reader.read();
      if (done) break;
      bytes += value.byteLength;
      if (bytes > maxBytes) {
        await reader.cancel();
        throw new Error('Link preview response too large');
      }
      json += decoder.decode(value, { stream: true });
    }
    json += decoder.decode();
  } finally {
    reader.releaseLock();
  }
  const data = parseLinkPreviewResponse(JSON.parse(json));
  if (!data) throw new Error('Invalid link preview response');
  return data;
}

View on GitHub (pinned to 2af30773ae)