Saltar al contenido principal

Building Static Sites on Fyso

Recommended stack, project setup, and deployment patterns for Fyso's static site hosting.

ToolVersionWhy
Astro5.xZero-JS by default, component islands, fast builds, .astro templates
Tailwind CSS3.xUtility-first CSS, no runtime, purged in production
TypeScript5.xType safety for data files and components

This combination produces small, fast static sites with no client-side JavaScript unless you explicitly opt in. Astro's output: 'static' mode generates plain HTML files — exactly what Fyso's file server expects.

Why not Next.js, Vite, or Hugo?

They all work. Fyso serves any static HTML from a ZIP file. But Astro + Tailwind is the sweet spot:

  • Next.js requires output: 'export' and loses SSR/API routes. If you need those, use Fyso's API directly.
  • Vite (vanilla) works for SPAs but requires manual routing setup.
  • Hugo is fast but uses Go templates — less familiar for TypeScript developers.
  • Astro gives you component-based development, data loading at build time, and zero JS shipped by default.

Quick Start

1. Create the project

npm create astro@latest my-site
cd my-site
npx astro add tailwind

This installs Astro, creates the project structure, and configures Tailwind CSS.

2. Verify the config

astro.config.mjs:

import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';

export default defineConfig({
integrations: [tailwind()],
output: 'static',
});

tailwind.config.mjs:

/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};

3. Build and deploy

npm run build
# Output goes to dist/

Deploy via MCP (from Claude Desktop or Claude Code):

> "Deploy my site from /path/to/my-site/dist to subdomain 'my-site'"

Or via curl:

cd my-site
zip -r dist.zip dist/
curl -X POST https://app.fyso.dev/api/sites/my-site/deploy \
-H "Authorization: Bearer $FYSO_API_KEY" \
-F "file=@dist.zip"

Your site is live at https://my-site.sites.fyso.dev.

Project Structure

my-site/
├── src/
│ ├── pages/
│ │ └── index.astro # Routes = file paths
│ ├── layouts/
│ │ └── Layout.astro # HTML wrapper (head, body)
│ ├── components/
│ │ ├── Header.astro # Reusable components
│ │ └── Footer.astro
│ ├── data/
│ │ └── content.json # Build-time data (loaded in frontmatter)
│ └── styles/
│ └── global.css # Global styles (Tailwind directives)
├── public/ # Static assets (copied as-is)
├── astro.config.mjs
├── tailwind.config.mjs
├── package.json
└── tsconfig.json

Key conventions

  • Pages live in src/pages/. File paths become URL paths: src/pages/about.astro/about.
  • Layouts wrap pages with shared HTML structure (head tags, nav, footer).
  • Components are .astro files with scoped styles and zero JS overhead.
  • Data files in src/data/ are imported in frontmatter and available at build time.

Patterns

Loading data at build time

Astro components have a frontmatter block (between --- fences) that runs at build time:

---
// This runs at build time, not in the browser
import items from '../data/items.json';

const sorted = items.sort((a, b) => b.date.localeCompare(a.date));
---

<ul>
{sorted.map(item => (
<li>{item.title} — {item.date}</li>
))}
</ul>

Fetching from Fyso API at build time

You can pull data from your Fyso entities during the build:

---
const API_URL = import.meta.env.FYSO_API_URL || 'https://app.fyso.dev/api';
const API_KEY = import.meta.env.FYSO_API_KEY;

const res = await fetch(`${API_URL}/entities/products/records?limit=50`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const { data: products } = await res.json();
---

<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{products.map(p => (
<div class="p-4 border rounded">
<h3>{p.data.name}</h3>
<p class="text-gray-600">{p.data.description}</p>
<span class="font-bold">${p.data.price}</span>
</div>
))}
</div>

Important: Entity fields are nested inside record.data, not at the top level. Access them as p.data.name, not p.name.

Set environment variables in a .env file (not committed):

FYSO_API_URL=https://app.fyso.dev/api
FYSO_API_KEY=fyso_ak_your_key_here

Custom color palette

Extend Tailwind with your brand colors:

// tailwind.config.mjs
export default {
content: ['./src/**/*.{astro,html,js,ts,tsx}'],
theme: {
extend: {
colors: {
brand: '#3b82f6',
surface: '#1e1e2e',
text: '#cdd6f4',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
};

Then use them in your templates:

<div class="bg-surface text-text">
<h1 class="text-brand">My Site</h1>
</div>

Multi-page sites

Add pages as .astro files in src/pages/:

src/pages/
├── index.astro → /
├── about.astro → /about
├── contact.astro → /contact
└── blog/
├── index.astro → /blog
└── first-post.astro → /blog/first-post

Fyso's Caddy config handles try_files with .html extension fallback, so /about correctly serves about.html.

Responsive design

Use Tailwind breakpoints. No JavaScript needed:

<!-- Stack on mobile, grid on desktop -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="p-4">Card 1</div>
<div class="p-4">Card 2</div>
<div class="p-4">Card 3</div>
</div>

<!-- Show/hide by screen size -->
<nav class="hidden md:flex">Desktop nav</nav>
<nav class="md:hidden">Mobile nav</nav>

Prefer CSS breakpoints (hidden/md:block) over JavaScript media queries. No hydration issues, no JS dependency.

Deployment

The deploy_static_site MCP tool handles everything:

  1. Build your site: npm run build
  2. Tell your agent: "Deploy my site from /path/to/dist to subdomain my-site"

The MCP tool zips the directory, uploads it, and returns the URL.

Via curl

npm run build
cd dist && zip -r ../site.zip . && cd ..
curl -X POST https://app.fyso.dev/api/sites/my-site/deploy \
-H "Authorization: Bearer $FYSO_API_KEY" \
-F "file=@site.zip"
rm site.zip

Via Makefile

Add a deploy target to your Makefile:

SUBDOMAIN ?= my-site
API_KEY ?= $(shell echo $$FYSO_API_KEY)

deploy: build
@cd dist && zip -qr /tmp/_deploy.zip . && cd ..
@curl -s -X POST "https://app.fyso.dev/api/sites/$(SUBDOMAIN)/deploy" \
-H "Authorization: Bearer $(API_KEY)" \
-F "file=@/tmp/_deploy.zip" | jq .
@rm /tmp/_deploy.zip

build:
npm run build

Redeployment

Deploying to an existing subdomain replaces the previous version. The deployment is atomic — users see either the old or new version, never a partial state.

Badge

Fyso injects a small "Creado con Fyso" badge into HTML files during deployment. This is automatic for free tier sites. The badge is a fixed-position element that links back to Fyso.

Limits

LimitValue
Compressed ZIP size50 MB
Decompressed size500 MB
Subdomain length1-63 characters
Subdomain charactersa-z, 0-9, -
Reserved subdomainswww, api, app, admin, mail, ftp, staging, test, dev, static, assets, cdn

Tips

  1. Keep builds small. Astro + Tailwind produces tiny bundles. Avoid shipping unnecessary assets.
  2. Use public/ for static assets. Files in public/ are copied directly — images, fonts, favicons.
  3. Data at build time, not runtime. Fetch from Fyso API in frontmatter, not in client-side JavaScript. This keeps your site fast and your API key out of the browser.
  4. Rebuild to update. Since the site is static, redeploy after data changes. For frequently changing data, consider client-side fetch with a public API endpoint instead.
  5. No server-side code. Fyso serves static files only. For dynamic behavior, use Fyso's REST API from client-side JavaScript or build an API-backed SPA.

Reference Implementation

The Cero dashboard at cero.sites.fyso.dev is built with this stack:

  • Astro 5.3.0 + Tailwind CSS 3.4.17
  • Custom color palette (dark theme with warm tones)
  • Data-driven content loaded from JSON files at build time
  • Multi-language support (ES/EN/JA) via localStorage
  • 8 components, zero client-side framework dependencies
  • Deployed via MCP deploy_static_site tool
Creado con Fyso