This is a scratch project - a static site built from MDX files using the scratch CLI.
scratch is a CLI tool that compiles MDX (Markdown + JSX) files into a static website. It uses Bun as the build tool and bundler, React for rendering, and Tailwind CSS for styling.
Run scratch --help to see all available commands.
scratch dev- Start development server with hot reloadscratch build- Build the static site todist/scratch preview- Preview the built site locallyscratch clean- Clean build artifacts
-v, --verbose- Verbose output for debugging-p, --port <port>- Custom port for dev/preview servers-n, --no-open- Don't auto-open browser
project/
├── pages/ # MDX and Markdown content (required)
│ ├── index.mdx # Homepage (resolves to /)
│ ├── Counter.tsx # Components can live alongside pages
│ └── posts/
│ └── hello.mdx # Resolves to /posts/hello/
├── src/ # React components and styles (optional)
│ ├── Button.jsx
│ ├── PageWrapper.jsx
│ ├── tailwind.css
│ └── markdown/ # Custom markdown renderers
├── public/ # Static assets (optional, copied as-is)
│ └── logo.png
└── dist/ # Build output (generated)
Place .mdx or .md files in pages/. MDX lets you use React components directly in Markdown:
---
title: My Page
description: A description for SEO
---
# Hello World
This is markdown with a <Button>React component</Button> inline.YAML frontmatter is automatically extracted and injected as HTML meta tags:
title- Page title and og:titledescription- Meta description and og:descriptionimage- og:imagekeywords- Meta keywordsauthor- Meta author
pages/index.mdx→/pages/about.mdx→/about/pages/posts/hello.mdx→/posts/hello/pages/posts/index.mdx→/posts/
The pattern: index.mdx resolves to its parent directory path, other files get their own directory.
Components in src/ or pages/ are automatically available in MDX files without importing them. Just use them:
# My Page
<MyComponent prop="value" />
<Button>Click me</Button>The build automatically injects the necessary imports.
Important: The component name must match the filename:
src/Button.jsx→<Button />workssrc/ui/Card.tsx→<Card />works (subdirectories are fine)pages/Counter.tsx→<Counter />works (co-located components)- But a component named
Buttondefined insidehelpers.jsxwill NOT auto-import
If two files have the same basename (e.g., src/Button.jsx and pages/Button.jsx), only one will be available.
Components can accept children in different ways:
Self-closing (no children):
<Counter />
<Chart data={myData} />Self-closing components are automatically wrapped in a <div className="not-prose"> wrapper, so they won't inherit Tailwind Typography styles. This is useful for components that render their own styled content (charts, forms, interactive widgets).
Inline children (text on the same line):
<Button>Click me</Button>
<Highlight>important text</Highlight>Block children (markdown with blank lines):
<Callout type="warning">
## Warning Title
This is a **markdown** paragraph inside the component.
- List item one
- List item two
</Callout>The blank lines after the opening tag and before the closing tag are required for block-level markdown to be parsed correctly.
Components can use Tailwind CSS utility classes - they're globally available:
// src/Card.jsx
export function Card({ children }) {
return (
<div className="p-4 rounded-lg shadow-md bg-white hover:shadow-lg transition-shadow">
{children}
</div>
);
}If you create a src/PageWrapper.jsx, it will automatically wrap all page content. Useful for layouts:
// src/PageWrapper.jsx
export default function PageWrapper({ children }) {
return (
<div className="max-w-2xl mx-auto p-8">
<nav>...</nav>
<main>{children}</main>
<footer>...</footer>
</div>
);
}Components in src/markdown/ override default Markdown element rendering:
Heading.tsx- Custom heading rendering (h1-h6)CodeBlock.tsx- Custom code block rendering with syntax highlighting
Files in public/ are copied directly to the build output. Reference them with absolute paths:
Scratch uses Tailwind Typography for markdown styling. The prose class is applied via PageWrapper.
- Size variants: Add
prose-sm,prose-lg,prose-xlin PageWrapper.jsx - Color themes: Add
prose-slate,prose-zinc,prose-neutral, etc.
Tailwind Typography supports element modifiers to override styling for specific element types directly in PageWrapper.jsx:
<div className="prose prose-a:text-blue-600 prose-a:hover:text-blue-800 prose-headings:font-bold">Available element modifiers:
prose-headings:- all headings (h1-h6)prose-h1:,prose-h2:, etc. - specific heading levelsprose-a:- linksprose-p:- paragraphsprose-blockquote:- blockquotesprose-code:- inline codeprose-pre:- code blocksprose-ol:,prose-ul:,prose-li:- listsprose-table:,prose-th:,prose-td:- tablesprose-img:,prose-figure:,prose-figcaption:- images
You can also add CSS overrides in src/tailwind.css:
.prose a {
@apply text-blue-600 hover:text-blue-800 no-underline;
}For more complex overrides (adding interactivity, conditional logic), create a custom component in src/markdown/:
- Create/edit a component (e.g.,
Link.tsx) - Export from
src/markdown/index.tsand add toMDXComponents
Example:
// src/markdown/Link.tsx
export default function Link({ href, children, ...props }) {
const isExternal = href?.startsWith('http');
return (
<a
href={href}
target={isExternal ? '_blank' : undefined}
rel={isExternal ? 'noopener noreferrer' : undefined}
{...props}
>
{children}
</a>
);
}Use not-prose class to exclude elements from typography styling.
These are generated and should be in .gitignore:
dist/- Build output.scratch-build-cache/- Build cache and auto-installed dependencies