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
- Log the raw response body and validate it against the schema requirements (must be JSON with a valid http/https 'url')
- Confirm the preview backend returns the documented schema and hasn't changed its API shape
- Check for proxies/CDNs intercepting and returning HTML (login pages, error pages) with 200 status
- 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
- Ensure the preview backend always returns application/json with a valid http(s) 'url' field
- Check content-type header is application/json before parsing
- Watch for proxies/CDNs returning HTML with 200 status (intercept and detect non-JSON bodies)
- Pin and test against the backend API version to catch schema drift early
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
- captcha_verification_failed
- email_token_not_found
- invalid_checkout_parameters
- Invalid config for module
- invalid_oauth_response
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)