Scratch requires no configuration so it's easy to get started:
# Install scratch
curl -fsSL https://scratch.dev/install.sh | bash
# Create a new project
scratch create
# Start the dev server
scratch devNow you're ready to start writing in pages/index.mdx.
Scratch uses an opinionated project structure and requires no boilerplate or configuration: just create a project, run the dev server, and start writing.
A simple Scratch project (created with scratch create my-scratch-project) looks like this:
Use scratch build to compile this project into a static website, like this one.
Markdown files live in pages/ and can be either MDX (.mdx) or vanilla Markdown (.md).
Scratch compiles each Markdown file into a static web page whose route is determined by your project's directory structure:
| File | URL |
|---|---|
pages/index.mdx | / |
pages/about.mdx | /about/ |
pages/posts/index.mdx | /posts/ |
pages/posts/hello.mdx | /posts/hello/ |
Component files and libraries can live anywhere in pages/ and src/. They are auto-detected by Scratch and don't need to be explicitly imported in your Markdown files as long as the component name (Counter) matches the component name (Counter.jsx or Counter.tsx).
pages/ can also contain static content like images. These are copied directly into your output directory (dist/) and will be served the same way your compiled Markdown is.
Add static content like a favicon or _redirects file to the public/ directory. These are copied directly into your output directory (dist/) just like static content in pages/.
src/ is for CSS files, components and JS/TS libraries. New Scratch projects will contain the following:
src/template/PageWrapper.jsx - your Markdown contents will be "wrapped" with this component. Modify it to change page headers, footers, nav bars and other global components.src/template/ - contains page template components like Header.jsx, Footer.jsx, Copyright.jsx, and ScratchBadge.jsx.src/tailwind.css - global styles. Edit this to change the look and feel of your compiled Markdown.src/markdown - a directory containing default Markdown components. For example, edit src/markdown/CodeBlock.tsx to change how Markdown code blocks are rendered.Scratch automatically installs build dependencies. You can add additional dependencies by editing package.json.
Create a new Scratch project.
scratch create [path]Options:
--no-src - Skip the src/ template directory--no-package - Skip package.json template--no-example - Skip example content filesStart the development server with live reload.
scratch dev [path]Options:
-p, --port <port> - Port for dev server (default: 5173)-n, --no-open - Don't open browser automatically-d, --development - Development mode (unminified, with source maps)-b, --base <path> - Base path for deployment (e.g., /mysite/)--static <mode> - Static file mode:
public - ignore static assets in pages/assets (default) - serve assets like images, but not javascript or typescript code files like lib.jsall - serve assets and code files statically--strict - Disable auto-injection of PageWrapper and imports--highlight <mode> - Syntax highlighting supported languages: off, popular, auto (default), allBuild the project for production.
scratch build [path]Options:
-o, --out-dir <path> - Output directory (default: dist)-d, --development - Development mode (unminified, with source maps)-b, --base <path> - Base path for deployment--no-ssg - Disable static site generation--static <mode> - Static file mode: public, assets, all--strict - Disable auto-injection of PageWrapper and imports--highlight <mode> - Syntax highlighting modePreview the production build locally.
scratch preview [path]Options:
-p, --port <port> - Port for preview server (default: 4173)-n, --no-open - Don't open browser automaticallyQuick preview of a single file or directory. Handy for e.g. reading README.md files.
scratch view <path>Options:
-p, --port <port> - Port for dev server (default: 5173)-n, --no-open - Don't open browser automaticallyUseful for viewing individual .md or .mdx files without setting up a full project.
Remove build artifacts.
scratch clean [path]Removes the dist/ and .scratch-build-cache/ directories.
Restore template files from built-in templates.
scratch checkout [file]Options:
-l, --list - List all available template files-f, --force - Overwrite existing files without confirmationUpdate Scratch to the latest version.
scratch updateThese options work with all commands:
-v, --verbose - Verbose output for debugging-q, --quiet - Quiet mode (errors only)--show-bun-errors - Show full Bun error stack traces--version - Show CLI versionAdd YAML frontmatter at the top of your MDX files to set page metadata. These properties are automatically converted to HTML meta tags for SEO and social sharing.
| Property | Description |
|---|---|
title | Page title. Sets <title>, og:title, and twitter:title |
description | Page description for SEO. Sets meta description, og:description, and twitter:description |
keywords | Keywords for SEO. Can be a string or array (arrays are joined with ", ") |
author | Page author. Sets meta[name="author"] |
robots | Robots directive (e.g., "index, follow") |
lang | HTML language attribute (e.g., "en") |
canonical | Canonical URL. Sets <link rel="canonical"> |
| Property | Description | Default |
|---|---|---|
image | Social sharing image URL. Sets og:image and twitter:image | - |
url | Canonical Open Graph URL. Sets og:url | - |
type | Content type. Sets og:type | "article" |
siteName | Site name. Sets og:site_name | - |
locale | Locale (e.g., "en_US"). Sets og:locale | - |
siteUrl | Base URL for resolving relative image paths | - |
| Property | Description | Default |
|---|---|---|
twitterCard | Card type. Sets twitter:card | "summary_large_image" |
twitterSite | Site's Twitter handle (e.g., "@mysite"). Sets twitter:site | - |
twitterCreator | Author's Twitter handle. Sets twitter:creator | - |
| Property | Description |
|---|---|
publishDate | Publication date (ISO format). Sets article:published_time |
modifiedDate | Last modified date (ISO format). Sets article:modified_time |
tags | Article tags. Can be string or array. Sets article:tag |
---
title: "My Page Title"
description: "A brief description for SEO and social sharing"
keywords: ["react", "markdown", "static site"]
author: "Your Name"
image: "/og-image.png"
type: "article"
siteName: "My Site"
locale: "en_US"
twitterCard: "summary_large_image"
twitterSite: "@mysite"
publishDate: "2025-01-01"
tags: ["tutorial", "guide"]
canonical: "https://example.com/my-page"
lang: "en"
siteUrl: "https://example.com"
---Note: Image URLs can be absolute (starting with http:// or https://) or relative paths. Relative paths are resolved against siteUrl if provided.
Components in src/ or pages/ are automatically available in MDX files:
# My Page
<Counter />
<Button>Click me</Button>The component filename must match the component name (e.g., Counter.tsx exports Counter).
Self-closing (wrapped in not-prose div):
<Chart />Inline children:
<Button>Click me</Button>Block children (requires blank lines):
<Callout>
## Warning
This is **markdown** inside a component.
</Callout>Scratch uses Tailwind CSS. Edit src/tailwind.css for global styles:
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
/* Custom styles */
.prose a {
@apply text-blue-600 hover:text-blue-800;
}Markdown content is styled with Tailwind Typography. Use prose modifiers in src/template/PageWrapper.jsx:
<div className="prose prose-lg prose-slate">
{children}
</div>Element modifiers: prose-h1:, prose-a:, prose-p:, prose-code:, prose-pre:, etc.
Use the not-prose class to exclude elements from typography styling:
<div className="not-prose">
<CustomComponent />
</div>Create src/template/PageWrapper.jsx to wrap all pages with a layout:
export default function PageWrapper({ children }) {
return (
<div className="max-w-2xl mx-auto p-8">
<nav>...</nav>
<main className="prose">{children}</main>
<footer>...</footer>
</div>
);
}Override default markdown rendering by creating components in src/markdown/:
// 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>
);
}Export from src/markdown/index.ts:
export { default as a } from './Link';
export { default as pre } from './CodeBlock';
export { default as h1, default as h2 } from './Heading';Code blocks are highlighted with Shiki. Control highlighting with the --highlight flag:
off - No syntax highlightingpopular - Common languages onlyauto - Detect from code blocks (default)all - All languagesWhen you run scratch build, Scratch compiles your MDX files into a fully static website. There's no server-side code running in production—everything is pre-rendered HTML, CSS, and JavaScript that can be hosted on any static file server.
Scratch supports defaults that make it easy to create beautiful & functional websites with your writing. Here's a description of the Scratch build pipeline:
Scratch auto-installs npm dependencies from your package.json using Bun. Dependencies are cached in .scratch-build-cache/ for faster subsequent builds.
For each .mdx or .md file in pages/, Scratch generates client and server entry files that import your content and wire up React rendering.
MDX files are compiled through a pipeline of remark and rehype plugins:
Remark plugins (process Markdown AST):
not-prose divs to prevent Tailwind Typography styles from affecting themRehype plugins (process HTML AST):
Scratch compiles your CSS using Tailwind CSS v4. Only the utilities actually used in your content are included in the final bundle.
Scratch builds a server bundle to pre-render each page. The "server bundle" doesn't actually run on a server; it's only used during the build process to generate pre-rendered HTML.
The client JavaScript is bundled with Bun's bundler. Output is minified for production with content-hashed filenames for cache busting.
Each page is rendered to static HTML with:
Files from public/ are copied to the output directory.
The final static site is written to dist/, ready for deployment to any static host.
Build your site and deploy the dist/ folder to any static host:
scratch buildFor subdirectory deployment, use the --base flag:
scratch build --base /my-site/Add these to .gitignore:
dist/
.scratch-build-cache/
node_modules/