Quartz Type Safety
Patterns and anti-patterns for TypeScript type safety in Quartz inline scripts.
Type Definitions
Global Type Augmentation
Quartz inline scripts run in a browser context with custom globals (e.g., window.addCleanup, custom events). TypeScript needs declaration files to understand these.
Solution: Create a centralized types/quartz.d.ts with proper declarations.
// types/quartz.d.ts
declare global {
interface Window {
addCleanup(fn: () => void): void;
spaNavigate(url: URL | FullSlug): void;
}
}See global-type-augmentation for the complete pattern.
Common Errors
1. Missing Type Definitions
Problem: TypeScript errors for SCSS modules, Window extensions, and custom events.
Symptoms:
TS2339: Property 'addCleanup' does not exist on type 'Window'
TS2307: Cannot find module '*.scss' or its corresponding type declarationsSolution: Create types/quartz.d.ts with:
- SCSS module declarations
- Window interface extensions
- Custom event type mappings
- Content data type definitions
2. Async Function Invocation
Problem: Calling a factory function without parentheses.
// WRONG - fetchData is a function, not a promise
const data = await fetchData;
// CORRECT - call the function
const data = await fetchData();Root Cause: fetchData is a factory function that returns a Promise. Without parentheses, you’re awaiting the function reference itself (which resolves immediately to the function).
Files Affected: graph.inline.ts, search.inline.ts
3. Library API Misuse
Problem: Using outdated or incorrect library method signatures.
// WRONG - old FlexSearch signature
index.addAsync(id++, { id, slug, title, content, tags });
// CORRECT - FlexSearch expects document as single parameter
index.addAsync({ id, slug, title, content, tags });Lesson: Always verify library APIs after version updates. TypeScript strict mode catches these at compile time.
4. Optional Field Handling
Problem: Accessing optional fields without null checks.
// WRONG - may be undefined
const tags = contentDetails.tags.join(', ');
// CORRECT - use null coalescing
const tags = (contentDetails.tags ?? []).join(', ');Context: ContentDetails fields (title, tags, content) are optional. Always provide defaults.
Anti-Patterns
Timing-Based Initialization
// WRONG - unreliable race condition workaround
requestAnimationFrame(() => init());
setTimeout(() => init(), 50);
setTimeout(() => init(), 200);
// CORRECT - use SPA navigation events
document.addEventListener('nav', () => init());Missing Error Boundaries
// WRONG - unhandled promise rejection
document.addEventListener('nav', async () => {
await initializeGraph();
});
// CORRECT - wrap in try-catch with component prefix
document.addEventListener('nav', async () => {
try {
await initializeGraph();
} catch (error) {
console.error('[Graph] Init failed:', error);
}
});Inline Type Assertions
// WRONG - bypasses type checking
const data = (await fetchData()) as any;
// CORRECT - proper type declaration
const data: ContentIndex = await fetchData();Best Practices
- Centralized type definitions - Single
types/directory for all declarations - Error boundary with component prefixes -
[Graph] Init failed:for easy debugging - Strict TypeScript checking - Catches runtime errors at compile time
- Build verification before deploy - CI workflow gates prevent broken deployments
- Null coalescing for optional fields - Default values prevent runtime errors
Related
- quartz-spa-navigation - SPA router patterns and cleanup system
- global-type-augmentation - Reusable type declaration pattern