| layout | docs |
|---|---|
| docsOrder | 100 |
| handlebars | false |
Use TypeScript for pages, layouts, data modules, and browser code without adding a separate compilation step.
Node.js strips types from server-side modules, esbuild handles browser bundles, and tsc checks types separately.
[[toc]]
- Use Node.js 22.18+ within the 22.x release line, or Node.js 24 and newer, as required by DOMStack v12.
These versions enable type stripping by default; no
NODE_OPTIONSflag is needed. - Seamlessly mix
.ts,.mts,.ctsfiles alongside.js,.mjs,.cjs. - No explicit compilation step needed—Node.js handles type stripping at runtime.
- Fully compatible with existing
domstackfile naming conventions. - Anywhere DOMStack loads JS files, it can now load TS files.
Anywhere you can use a .js, .mjs, or .cjs file in DOMStack, you can use the corresponding .ts, .mts, or .cts extension.
Tip
Prefer the regular .ts and .js extensions with "type": "module" in package.json.
Use the module-format escape-hatch extensions only when an individual file must override the package's module format.
When running in a Node.js context, type-stripping is used.
When running in a web client context, esbuild type stripping is used.
Type stripping provides 0 type checking, so be sure to set up tsc and tsconfig.json so you can catch type errors while editing or in CI.
Install @voxpelli/tsconfig, which enables type checking in .js and .ts files and configures TypeScript for --noEmit.
Extend its Node.js 22 baseline with DOMStack's type-stripping and client-TSX settings:
You can use domstack's built-in types to strongly type your layout, page, and template functions.
Runtime values are imported from @domstack/static; types are imported from the dedicated @domstack/static/types.js entry.
The following types are available:
// src/types.ts
import type {
// Type a synchronous or asynchronous layout default export
LayoutFunction,
// Require a layout default export to return a promise
AsyncLayoutFunction,
// Type a synchronous or asynchronous global.data.ts default export
GlobalDataFunction,
// Require a global.data.ts default export to return a promise
AsyncGlobalDataFunction,
// Type a synchronous or asynchronous TypeScript page function
PageFunction,
// Require a TypeScript page function to return a promise
AsyncPageFunction,
// Type a template that returns one or more buffered outputs
TemplateFunction,
// Type an async-generator template that yields outputs incrementally
TemplateAsyncIterator,
// Type a generated-pages factory in a *.pages.ts file
PagesFunction,
// Describe one initialized entry in the pages collection
PageData,
// Describe metadata for the current page
PageInfo,
// Describe the current *.template.ts file
TemplateInfo,
// Describe the current *.pages.ts file
PagesFileInfo,
// Describe one page returned by a generated-pages module
GeneratedPageDefinition,
// Type a helper that receives a layout function's arguments
LayoutFunctionParams,
// Type a helper that receives global.data.ts arguments
GlobalDataFunctionParams,
// Type a helper that receives a page function's arguments
PageFunctionParams,
// Type a helper that receives a template function's arguments
TemplateFunctionParams,
// Type a helper that receives a generated-pages factory's arguments
PagesFunctionParams,
} from '@domstack/static/types.js'Note
Use PageFunction, LayoutFunction, TemplateFunction, and GlobalDataFunction for ordinary synchronous or asynchronous implementations.
Their Async* variants are available when a type must specifically require a promise return value, including JSDoc annotations directly on async functions.
PagesFunction supports normal functions, async functions, and async generators.
The function types are generic and accept variable shapes that you can develop and share between files.
The data and parameter types (PageData, PageInfo, TemplateInfo, PagesFileInfo, GeneratedPageDefinition, and *FunctionParams) are useful when you want to annotate variables or helper functions that receive these objects without using the function types directly:
// src/page-utils.ts
import type { GlobalDataFunctionParams, PageData, PageInfo } from '@domstack/static/types.js'
function getPublishedPages({ pages }: GlobalDataFunctionParams): PageData[] {
return pages.filter((p: PageData) => {
const info: PageInfo = p.pageInfo
return !info.draft
})
}PageFunction, LayoutFunction, TemplateFunction, and PagesFunction support additional type parameters for precise input, data, and return type control:
PageFunction<T, U, D>
T- The type of variables passed to the page (required)U- The return type of the page function (optional, defaults toany)D- The declared global-data shape (optional, defaults toRecord<string, unknown>)
LayoutFunction<T, U, V, D>
T- The type of variables passed to the layout (required)U- The type of content received from pages aschildren(optional, defaults toany)V- The return type of the layout function (optional, defaults tostring)D- The declared global-data shape (optional, defaults toRecord<string, unknown>)
TemplateFunction<T, D>
T- The global vars passed to the template (required)D- The declared global-data shape (optional, defaults toRecord<string, unknown>)
PagesFunction<T, U, V, D, P>
T- The vars added to generated pages (optional, defaults toRecord<string, any>)U- The static children or inline page-function return type (optional, defaults tostring)V- The default and global vars received by the pages factory (optional, defaults toRecord<string, any>)D- The factory's declared global-data shape (optional, defaults toRecord<string, unknown>)P- Inline pages' declared global-data shape (optional, defaults toDfor convenience; set it independently when factory and page subscriptions differ)
Each layout's input, output, and data types are independent of its parent and page. DOMStack resolves layout names at runtime, so it cannot statically prove that two separately declared layout modules have compatible content types. Manual function calls do receive normal TypeScript argument checking.
This allows pages to return custom types (like VDOM or JSON), ensures layouts produce HTML strings, and keeps generated-page vars separate from the vars used to create them:
// src/rendering-types.ts
// Define custom types
type VDOMNode = {
type: string
props: Record<string, any>
children: Array<VDOMNode | string>
}
// Page returns VDOM
const page: PageFunction<{title: string}, VDOMNode> = ({ vars }) => ({
type: 'h1',
props: {},
children: [vars.title]
})
// Layout accepts VDOM, returns HTML string
const layout: LayoutFunction<{site: string}, VDOMNode, string> = ({ children }) => {
const html = renderVDOM(children) // Convert VDOM to HTML
return `<html><body>${html}</body></html>`
}