|
| 1 | +--- |
| 2 | +title: Deployment |
| 3 | +description: Deploy Magnetic apps — SSR, static site generation, and hybrid pre-render. |
| 4 | +layout: docs |
| 5 | +order: 5 |
| 6 | +--- |
| 7 | + |
| 8 | +# Deployment |
| 9 | + |
| 10 | +Magnetic supports three deployment modes. Choose based on your app's needs: |
| 11 | + |
| 12 | +| Mode | Command | Use case | Server required | |
| 13 | +|------|---------|----------|-----------------| |
| 14 | +| **SSR** | `magnetic push` | Dynamic apps with real-time data, user sessions | Yes | |
| 15 | +| **Hybrid** | `magnetic push` + `prerender` in config | Mix of static content + live interactive pages | Yes | |
| 16 | +| **SSG** | `magnetic push --static` | Docs, blogs, marketing pages — zero JS | No | |
| 17 | + |
| 18 | +## SSR Deployment |
| 19 | + |
| 20 | +The default. Your app is rendered on demand for every request with full interactivity — actions, SSE, real-time data, user sessions. |
| 21 | + |
| 22 | +```bash |
| 23 | +# Build and deploy in one command |
| 24 | +magnetic push --name my-app --server https://api.fujs.dev |
| 25 | + |
| 26 | +# Or specify a directory |
| 27 | +magnetic push --dir apps/my-app --name my-app --server https://api.fujs.dev |
| 28 | +``` |
| 29 | + |
| 30 | +Your app gets a subdomain: `https://my-app.fujs.dev` |
| 31 | + |
| 32 | +What gets deployed: your pages, components, state, config, and static assets. One command, no Docker, no CI pipeline. Deploys hot-reload instantly. |
| 33 | + |
| 34 | +## Static Site Generation (SSG) |
| 35 | + |
| 36 | +Pre-renders your pages to **pure static HTML** at build time. No JavaScript, no SSE, no server required. Deploy to the Magnetic platform or any static host. |
| 37 | + |
| 38 | +```bash |
| 39 | +# Build and deploy static site |
| 40 | +magnetic push --static --name my-app --server https://api.fujs.dev |
| 41 | + |
| 42 | +# Or build only (outputs to dist/) |
| 43 | +magnetic build --static |
| 44 | +``` |
| 45 | + |
| 46 | +SSG is the right choice when your content doesn't change per-request. The same `content/*.md` files and page components work in both modes — the only difference is the output. |
| 47 | + |
| 48 | +### How SSG Works |
| 49 | + |
| 50 | +1. **Build**: The CLI bundles your app (pages, state, JSX runtime, CSS framework) into `app.js` |
| 51 | +2. **Render**: Each route is rendered through the same server pipeline used for SSR |
| 52 | +3. **Extract**: `<Head>` components become `<head>` elements, design tokens become inlined `<style>` |
| 53 | +4. **Write**: Each page is written as `{route}/index.html` |
| 54 | + |
| 55 | +Each route produces a self-contained HTML file with inlined CSS. Navigation uses plain `<a href>` links — standard browser navigation, no JavaScript needed. |
| 56 | + |
| 57 | +### Content Pipeline |
| 58 | + |
| 59 | +SSG works with the `content/` folder — the same markdown files used in SSR mode: |
| 60 | + |
| 61 | +``` |
| 62 | +my-app/ |
| 63 | + content/ |
| 64 | + getting-started.md |
| 65 | + components.md |
| 66 | + api-reference.md |
| 67 | + pages/ |
| 68 | + IndexPage.tsx |
| 69 | + [slug].tsx ← dynamic route for /:slug |
| 70 | + server/ |
| 71 | + state.ts |
| 72 | + magnetic.json |
| 73 | + design.json |
| 74 | +``` |
| 75 | + |
| 76 | +Each `.md` file with frontmatter becomes a route: |
| 77 | + |
| 78 | +```markdown |
| 79 | +--- |
| 80 | +title: Getting Started |
| 81 | +description: Build your first Magnetic app |
| 82 | +order: 1 |
| 83 | +--- |
| 84 | + |
| 85 | +# Getting Started |
| 86 | + |
| 87 | +Your markdown content here... |
| 88 | +``` |
| 89 | + |
| 90 | +### Lazy Content Mode |
| 91 | + |
| 92 | +For sites with hundreds or thousands of pages, baking all content into the JS bundle is wasteful. **Lazy content mode** keeps only the metadata index in the bundle and loads each `.md` file on demand during SSG: |
| 93 | + |
| 94 | +```bash |
| 95 | +# Default: all content baked into bundle (~161KB for 5 pages) |
| 96 | +magnetic build --static |
| 97 | + |
| 98 | +# Lazy: metadata index only, .md loaded on demand (~30KB bundle) |
| 99 | +magnetic build --static --lazy-content |
| 100 | +``` |
| 101 | + |
| 102 | +#### Performance at Scale |
| 103 | + |
| 104 | +| Pages | Bundle mode | Lazy mode | |
| 105 | +|-------|------------|-----------| |
| 106 | +| 5 | 161KB bundle, 8ms render | 30KB bundle, 8ms render | |
| 107 | +| 100 | ~500KB bundle | 30KB bundle | |
| 108 | +| 1,000 | ~5MB bundle | 30KB bundle | |
| 109 | +| 10,000 | impractical | **30KB bundle, ~15s total** | |
| 110 | + |
| 111 | +Benchmark (M1 MacBook Air): **10,006 pages in ~15 seconds** (1.5ms per page). The bundle stays at 30KB regardless of content count. |
| 112 | + |
| 113 | +The key advantage: **adding a new `.md` file requires zero rebuild** in lazy mode. The bundle never changes — only the SSG output step runs. |
| 114 | + |
| 115 | +### Deploy SSG to External Hosts |
| 116 | + |
| 117 | +The `dist/` directory is deployable to any static host: |
| 118 | + |
| 119 | +```bash |
| 120 | +# Netlify |
| 121 | +netlify deploy --dir=dist --prod |
| 122 | + |
| 123 | +# Cloudflare Pages |
| 124 | +npx wrangler pages deploy dist |
| 125 | + |
| 126 | +# GitHub Pages |
| 127 | +# Copy dist/ to your gh-pages branch |
| 128 | + |
| 129 | +# Any static server |
| 130 | +npx http-server dist -p 3000 |
| 131 | +``` |
| 132 | + |
| 133 | +## Hybrid Pre-render |
| 134 | + |
| 135 | +For apps that mix static content and dynamic pages — like a news site with blog posts AND live dashboards — you can **pre-render specific routes** at deploy time while keeping the rest fully dynamic. |
| 136 | + |
| 137 | +Pre-rendered pages are served instantly from disk. Everything else renders on demand with full interactivity. |
| 138 | + |
| 139 | +### Configuration |
| 140 | + |
| 141 | +Add a `prerender` array to `magnetic.json`: |
| 142 | + |
| 143 | +```json |
| 144 | +{ |
| 145 | + "name": "my-app", |
| 146 | + "prerender": ["/", "/about", "/blog/*"] |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +When you run `magnetic push`, the CLI: |
| 151 | +1. Builds the SSR bundle as normal |
| 152 | +2. Pre-renders the listed routes to static HTML |
| 153 | +3. Deploys both the bundle AND the pre-rendered pages |
| 154 | + |
| 155 | +The platform checks for a pre-rendered file first. If one exists, it's served instantly. If not, the server renders on demand as usual. |
| 156 | + |
| 157 | +### Glob Patterns |
| 158 | + |
| 159 | +Use `/*` to pre-render all content under a prefix: |
| 160 | + |
| 161 | +| Pattern | Expands to | |
| 162 | +|---------|-----------| |
| 163 | +| `"/"` | Just the homepage | |
| 164 | +| `"/about"` | Single route | |
| 165 | +| `"/blog/*"` | All content slugs under `blog/` | |
| 166 | +| `"/docs/*"` | All content slugs under `docs/` | |
| 167 | + |
| 168 | +Globs expand against your `content/` directory slugs and static page routes. Dynamic routes (`:param`) and catch-all routes are skipped. |
| 169 | + |
| 170 | +### Example: News Site |
| 171 | + |
| 172 | +```json |
| 173 | +{ |
| 174 | + "name": "my-news-site", |
| 175 | + "server": "https://api.fujs.dev", |
| 176 | + "prerender": ["/", "/about", "/articles/*", "/categories/*"], |
| 177 | + "data": { |
| 178 | + "live-feed": { |
| 179 | + "url": "https://api.example.com/live", |
| 180 | + "type": "sse", |
| 181 | + "page": "/live" |
| 182 | + } |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +| Route | Behavior | |
| 188 | +|-------|----------| |
| 189 | +| `/`, `/about` | Served as static HTML (instant, no compute) | |
| 190 | +| `/articles/*`, `/categories/*` | Pre-rendered from `content/` markdown (instant) | |
| 191 | +| `/live` | Rendered on demand with real-time SSE updates (dynamic) | |
| 192 | + |
| 193 | +### Content Updates |
| 194 | + |
| 195 | +Pre-rendered pages are generated at **deploy time**. To update content: |
| 196 | + |
| 197 | +1. Edit your `content/*.md` files |
| 198 | +2. Run `magnetic push` again |
| 199 | +3. The CLI re-renders all pre-render routes with the new content |
| 200 | + |
| 201 | +The pre-render step adds only milliseconds to the deploy process. |
| 202 | + |
| 203 | +## Navigation Across Modes |
| 204 | + |
| 205 | +| Mode | Navigation behavior | |
| 206 | +|------|-------------------| |
| 207 | +| **SSR** | `<Link>` uses client-side navigation — no full page reload | |
| 208 | +| **SSG** | Plain `<a href>` links — standard browser navigation, zero JS | |
| 209 | +| **Hybrid** | Pre-rendered pages use `<a href>`, dynamic pages use client-side navigation | |
| 210 | + |
| 211 | +Your page components work identically in all modes. The `href` attribute on `<Link>` and `<a>` is what matters. |
| 212 | + |
| 213 | +## Choosing a Mode |
| 214 | + |
| 215 | +### Choose SSR when: |
| 216 | +- Every page needs dynamic rendering |
| 217 | +- You have few pages and server compute is negligible |
| 218 | +- All pages use real-time data or user-specific content |
| 219 | + |
| 220 | +### Choose Hybrid when: |
| 221 | +- Your site has hundreds or thousands of content pages (blog posts, docs, articles) |
| 222 | +- Some pages need real-time data, user sessions, or interactivity |
| 223 | +- You want content pages to load instantly without server compute |
| 224 | +- You want to deploy everything as a single app |
| 225 | + |
| 226 | +### Choose SSG when: |
| 227 | +- Every page is static (docs site, marketing site, blog) |
| 228 | +- You don't need any server-side interactivity |
| 229 | +- You want zero server cost |
| 230 | + |
| 231 | +The `content/` folder is the single source of truth in all modes — same markdown files, same `getContent()`/`listContent()` API, same page components. Only the serving behavior differs. |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +← [Previous: CSS & Styling](/css-styling) · **Chapter 5** · [Next: Benchmarks →](/benchmarks) |
0 commit comments