How to Unify Your Multi-Site Web Stack Using Dart and Jaspr: A Step-by-Step Migration Guide

By ⚡ min read
<h2>Introduction</h2> <p>Building and maintaining multiple websites with different frameworks and languages can become a significant burden. The Dart team faced this exact challenge: their three primary sites—<strong>dart.dev</strong>, <strong>flutter.dev</strong>, and <strong>docs.flutter.dev</strong>—were each built with separate, non-Dart tools. The documentation sites relied on Eleventy (Node.js), while flutter.dev used Wagtail (Python/Django). This fragmentation made contributions difficult, increased setup friction, and limited code sharing. The solution was to migrate everything to <strong>Jaspr</strong>, an open-source Dart web framework that supports server-side rendering (SSR), client-side rendering (CSR), and static site generation (SSG). This guide walks you through the same process: from identifying the problem to deploying a unified stack, all using Dart and Jaspr.</p><figure style="margin:20px 0"><img src="https://picsum.photos/seed/2029830812/800/450" alt="How to Unify Your Multi-Site Web Stack Using Dart and Jaspr: A Step-by-Step Migration Guide" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px"></figcaption></figure> <h2>What You Need</h2> <ul> <li><strong>Dart SDK</strong> (version 3.0 or later) – installed and configured.</li> <li><strong>Jaspr CLI</strong> – install via <code>dart pub global activate jaspr_cli</code>.</li> <li><strong>Existing websites</strong> (or a plan to create multiple sites) that you want to unify.</li> <li><strong>Basic knowledge of Dart and Flutter widget concepts</strong> (optional but helpful).</li> <li><strong>A version control system</strong> (e.g., Git) to track changes.</li> <li><strong>A deployment platform</strong> (e.g., Firebase Hosting, Vercel, or your own server) that supports static or server-rendered Dart.</li> </ul> <h2>Step-by-Step Migration to Jaspr</h2> <h3>Step 1: Assess Your Current Stack and Identify Pain Points</h3> <p>Before migrating, document each site’s current framework, hosting setup, and content model. Ask yourself:</p> <ul> <li>Which sites use different languages or toolchains? (e.g., Node.js, Python, Ruby)</li> <li>How much custom interactivity exists (quizzes, code samples, etc.)?</li> <li>What is the contributor experience like? Do you need to learn multiple ecosystems?</li> </ul> <p>In the Dart team’s case, the documentation sites used Eleventy (Node.js) and flutter.dev used Wagtail (Python). This fragmented setup forced contributors to switch contexts and limited code reuse. Identifying these friction points will justify and guide your migration.</p> <h3>Step 2: Choose a Unified Framework – Jaspr</h3> <p>Jaspr is a Dart-based web framework that feels natural to Flutter developers. It supports SSR, CSR, and SSG, making it ideal for a mixed site portfolio. Why Jaspr?</p> <ul> <li><strong>Flutter skills transfer directly</strong> – its component model mirrors Flutter widgets.</li> <li><strong>DOM-compatible</strong> – you write HTML and CSS but with Dart syntax.</li> <li><strong>Open-source</strong> and actively maintained.</li> <li><strong>No extra toolchain</strong> – only Dart is needed.</li> </ul> <h3>Step 3: Set Up the Jaspr Project Structure</h3> <p>Create a new Jaspr project for your main site (e.g., <code>jaspr create my_unified_site</code>). This generates a standard structure with <code>lib/</code>, <code>web/</code>, and <code>pubspec.yaml</code>. For multiple sites, consider a monorepo using Dart packages. For example, you can have:</p> <pre><code>my-sites/ shared-components/ site-dart/ site-flutter/ site-docs/</code></pre> <p>Each site is a separate Jaspr project that imports widgets from <code>shared-components</code>. This allows you to reuse code across all sites while keeping content separate.</p> <h3>Step 4: Port Your Content and Components to Jaspr</h3> <p>Start with a static version of your most complex page. Jaspr components look like this:</p> <pre><code>class FeatureCard extends StatelessComponent { final String title; final String description; FeatureCard({required this.title, required this.description, super.key}); @override Component build(BuildContext context) { return div(classes: 'feature-card', [ h3([text(title)]), p([text(description)]), ]); } }</code></pre> <p>If you already have Flutter knowledge, this syntax will be familiar. For each page:</p> <ul> <li>Convert HTML templates into Jaspr components.</li> <li>Port any JavaScript interactivity to Dart (using <code>dart:html</code> or Jaspr’s built-in state management).</li> <li>Manage media assets and routing using Jaspr’s router or a custom setup.</li> </ul> <h3>Step 5: Implement Server-Side Rendering or Static Generation</h3> <p>Jaspr supports both SSR (for dynamic content) and SSG (for fast, cached pages). For documentation sites with mostly static content, SSG works well. For flutter.dev with CMS-like features, SSR might be better. Configure in <code>jaspr.config.yaml</code>:</p> <pre><code>modes: static: true # enable static generation for specific routes server: true # enable server-side rendering for others</code></pre> <p>You can even combine both: statically generate most pages and use SSR for interactive components like quizzes or code executors.</p> <h3>Step 6: Add Interactivity with Dart</h3> <p>The team wanted richer code samples and quizzes. With Jaspr, you can add client-side interactivity using Dart event handlers and state management. For example, a quiz component:</p> <pre><code>class Quiz extends StatefulComponent { @override State<Quiz> createState() => _QuizState(); } class _QuizState extends State<Quiz> { int score = 0; void answerCorrect() { setState(() => score++); } @override Component build(BuildContext context) { return div([ button(onClick: answerCorrect, [text('Correct')]), p([text('Score: $score')]), ]); } }</code></pre> <p>This eliminates the need for separate JavaScript files and keeps all logic in Dart.</p> <h3>Step 7: Test, Iterate, and Deploy</h3> <p>Run <code>jaspr serve</code> locally to preview your unified sites. Ensure all links, assets, and interactive features work across devices. Once satisfied, build for production:</p> <pre><code>jaspr build</code></pre> <p>This generates a <code>build/</code> folder with static files (if using SSG) or a server bundle (if using SSR). Deploy to your preferred platform. The Dart team used the same deployment pipelines but simplified them because only one toolchain was needed.</p> <h2>Tips for a Smooth Migration</h2> <ul> <li><strong>Start small</strong> – migrate one site first, then the others. This reduces risk and lets you refine your workflow.</li> <li><strong>Leverage shared components</strong> – create a package of reusable widgets (headers, footers, cards) to avoid duplication.</li> <li><strong>Keep your existing content</strong> – Jaspr can consume Markdown or YAML files if you want to keep content separate from code.</li> <li><strong>Use version control</strong> – commit often and use feature branches for each site migration.</li> <li><strong>Engage with the community</strong> – Jaspr is open-source; check their GitHub for examples and ask questions.</li> <li><strong>Test interactivity thoroughly</strong> – since you’re moving from imperative JavaScript to declarative Dart, edge cases may arise.</li> <li><strong>Monitor performance</strong> – compare Lighthouse scores before and after migration to ensure no degradation.</li> <li><strong>Document the new architecture</strong> – help your team and future contributors understand the unified stack.</li> </ul> <p>By following these steps, you can replicate the Dart team’s success: a unified, Dart-only web stack that simplifies contributions, reduces cognitive load, and opens the door to richer interactive features. Jaspr makes it possible to bring the power of Flutter to the DOM without leaving your language.</p>