TypeScript
TypeScript setup, JSX runtimes, and type imports in Avalon.
Avalon is built with TypeScript and provides types for all its APIs. Pages, layouts, and islands are all .tsx or .ts files by default.
Type imports
The main types come from @useavalon/avalon:
import type { LayoutProps } from '@useavalon/avalon';
Client-side component types come from @useavalon/avalon/client:
import type { ImageProps } from '@useavalon/avalon/client';
JSX runtime
The filename selects the JSX runtime. Avalon injects @jsxImportSource during the build, and the scaffolded tsconfig gives the editor the same mapping:
| Framework | Extension | JSX runtime |
|---|---|---|
| Preact | .tsx, .jsx, .preact.tsx | preact (or react when core is "react") |
| React | .react.tsx | react |
| Solid | .solid.tsx | solid-js |
| Qwik | .qwik.tsx | @builder.io/qwik |
| Vue | .vue | — |
| Svelte | .svelte | — |
| Lit | .lit.ts | — (decorators, not JSX) |
Plain .tsx follows core (preact by default). An explicit /** @jsxImportSource … */ at the top of a file overrides that choice.
// src/components/Counter.react.tsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
// src/components/Counter.solid.tsx
import { createSignal } from 'solid-js';
export default function Counter() {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>;
}
tsconfig
A minimal tsconfig.json for an Avalon project:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strict": true,
"skipLibCheck": true,
"paths": {
"@shared/*": ["./app/shared/*"],
"@modules/*": ["./app/modules/*"]
}
},
"include": ["app/**/*"]
}
The paths should match the resolve.alias entries in your Vite config.
Typing page props
Pages that use dynamic routes receive an event prop:
interface PageProps {
event: {
context: {
params?: Record<string, string>;
};
};
}
export default async function BlogPost({ event }: PageProps) {
const slug = event.context.params?.slug;
// ...
}
Typing layout props
import type { LayoutProps } from '@useavalon/avalon';
export default function MyLayout({ children, frontmatter }: LayoutProps) {
const title = frontmatter?.title as string | undefined;
// children: ComponentChildren
// frontmatter: Record<string, unknown>
}
Typing island props
Island props must be serializable (no functions, no class instances). TypeScript helps enforce this:
interface ChartProps {
data: number[];
label: string;
color?: string;
}
export default function Chart({ data, label, color = '#1F6AD3' }: ChartProps) {
// ...
}