Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@
{ "type": "build", "runFrequency": 5 },
{ "type": "dependencies" }
]
},
"app": {
"package": "app-mastro",
"buildScript": "build",
"buildOutputDir": "generated",
"measurements": [{ "type": "ssr" }]
}
},
{
Expand Down
3 changes: 3 additions & 0 deletions packages/app-mastro/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["ms-fast.fast-tagged-templates"]
}
40 changes: 40 additions & 0 deletions packages/app-mastro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Mastro Template Basic for Node.js

This is a basic TypeScript template for [Mastro](https://mastrojs.github.io) when using [Node.js](https://nodejs.org).

Click the green **Use this template** button in the top right to create your own copy of this repository. Then clone the **Code** to your computer.

## Run locally

If you have multiple projects on your computer that require different Node.js versions, you should install a tool to manage those version for you; for example [Volta](https://volta.sh/) (see [pnpm Support](https://docs.volta.sh/advanced/pnpm)).

Mastro requires Node.js >=24 (unless you want to install a [`URLPattern` polyfill](https://www.npmjs.com/package/urlpattern-polyfill)).

[JSR recommends](https://jsr.io/docs/npm-compatibility#installing-and-using-jsr-packages) to use `pnpm`.

The first time, you need to:

pnpm install

After that, to start the server:

pnpm run start

and open <http://localhost:8000> in your browser.

To generate the whole static site (this will create a `generated` folder):

pnpm run generate

## Next steps

To see how Mastro works, [follow the guide](https://mastrojs.github.io/guide/server-side-components-and-routing/).

To make sure you're using the latest Mastro packages:

pnpm update "@mastrojs/*" --latest

## Deploy to production

- [Deploy static site](https://mastrojs.github.io/guide/deploy/#deploy-static-site-with-ci%2Fcd)
- [Deploy server](https://mastrojs.github.io/guide/deploy/#deploy-server-to-production)
21 changes: 21 additions & 0 deletions packages/app-mastro/components/Layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Html, html } from '@mastrojs/mastro'

interface Props {
children: Html
title: string
}

export const Layout = (props: Props) => html`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>${props.title}</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<h1>${props.title}</h1>
${props.children}
</body>
</html>
`
26 changes: 26 additions & 0 deletions packages/app-mastro/handlers/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { testData } from '../../testdata/src/ssr.ts'
import { html, htmlToResponse } from '@mastrojs/mastro'
import { Layout } from '../components/Layout.ts'

export const GET = async () => {
const entries = await testData()
return htmlToResponse(
Layout({
title: 'Test',
children: html`
<table>
<tbody>
${entries.map(
(entry) => html`
<tr>
<td>${entry.id}</td>
<td>${entry.name}</td>
</tr>
`,
)}
</tbody>
</table>
`,
}),
)
}
21 changes: 21 additions & 0 deletions packages/app-mastro/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "app-mastro",
"private": true,
"type": "module",
"scripts": {
"dev": "node --watch server.ts",
"build": "node node_modules/@mastrojs/mastro/src/generator.js",
"type-check": "tsc"
},
"dependencies": {
"@mastrojs/mastro": "jsr:^0",
"@remix-run/node-fetch-server": "^0.11"
},
"devDependencies": {
"@types/node": "^24",
"typescript": "^5"
},
"engines": {
"node": ">=24.12"
}
}
156 changes: 156 additions & 0 deletions packages/app-mastro/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions packages/app-mastro/routes/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
html {
font-family: sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6 {
word-break: break-word;
text-wrap-style: pretty;
}

p {
hyphens: auto;
word-break: break-word;
}

img {
max-width: 100%;
}

@view-transition {
navigation: auto;
}
27 changes: 27 additions & 0 deletions packages/app-mastro/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as http from 'node:http'
import { createRequestListener } from '@remix-run/node-fetch-server'
import { Mastro } from '@mastrojs/mastro/server'
import { GET as getHome } from './handlers/home.ts'

// This is using Mastro's programmatic (Express-like) router
// because the default file-based router requires the
// current working directory to be the project root, which isn't
// always the case in this pnpm monorepo.

export const handler = new Mastro<unknown, void>()
.get('/', getHome)
.createHandler()

const port = 8000

if (import.meta.main) {
const server = http.createServer(createRequestListener(handler))

server.on('error', (e) => {
console.error(e)
})

server.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})
}
15 changes: 15 additions & 0 deletions packages/app-mastro/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"module": "NodeNext",
"moduleResolution": "nodenext",
"noEmit": true,
"skipLibCheck": true,
"strict": true,
"verbatimModuleSyntax": true,

"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}
11 changes: 11 additions & 0 deletions packages/stats-generator/src/ssr/handlers/mastro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { packagesDir } from '../../constants.ts'
import type { SSRHandler } from '../types.ts'

export async function buildMastroHandler(): Promise<SSRHandler> {
const entryPath = join(packagesDir, 'app-mastro', 'server.ts')
const entryUrl = pathToFileURL(entryPath).href
const { handler } = await import(entryUrl)
return { type: 'web', handler }
}
7 changes: 7 additions & 0 deletions packages/stats-generator/src/ssr/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { runBenchmark } from './run-benchmark.ts'
import { buildAstroHandler } from './handlers/astro.ts'
import { buildMastroHandler } from './handlers/mastro.ts'
import { buildNuxtHandler } from './handlers/nuxt.ts'
import { buildSvelteKitHandler } from './handlers/sveltekit.ts'
import { buildNextJSHandler } from './handlers/nextjs.ts'
Expand All @@ -22,6 +23,12 @@ const SSR_FRAMEWORKS: SSRFrameworkConfig[] = [
package: 'app-astro',
buildHandler: buildAstroHandler,
},
{
name: 'mastro-ssr',
displayName: 'Mastro SSR',
package: 'app-mastro',
buildHandler: buildMastroHandler,
},
{
name: 'nuxt-ssr',
displayName: 'Nuxt SSR',
Expand Down
Loading