Building a Personal Site With MonsterUI and FastHTML

Profile

Erik Gaasedelen

February 17, 2025

444 views

Share this post

Making solo webapp development possible

If you're like me, you have a graveyard of personal projects that you started but never finished. It's not the end of the world—I've learned a lot from unfinished work—but it's not always satisfying. Recently, I wanted to change that, so I dove deep into React, FastAPI, AWS, and Terraform. Again, I learned a ton, but touching every part of a full-stack app meant that nothing was great, especially web styling. I can try to learn flexbox over and over, yet it never seems to stick in my brain.


Full-stack development with this stack also takes forever. I remember spending five hours just figuring out how to implement third-party OAuth sign-in. Fortunately, there's a much better way, and I'm very excited to share it with you.


This entire site is built with FastHTML and MonsterUI—two relatively new libraries that make full-stack web app development a breeze. Take this site as an example: it has a database, authentication, contact forms, mobile responsiveness, and light/dark mode—far more than I ever dreamed of implementing in my own project. And, of course, blog support!


It still required effort, but I built all of this in about two weeks of evening work and some weekend free time. I shouldn't have been able to make something like this myself in such a short period. 😄 If I can do it, you can too. There's so much to cover, but let's start with a high-level look at what FastHTML and MonsterUI are and why they make me so much more productive.

Reducing Integration Costs

On my front page, you can see cards displaying the latest blog posts. These query the database and fill in relevant information. In FastAPI, you would typically do something like this to return JSON:

@app.get("/api/blogs")
async def get_blogs():
    return [
        {
            "title": "Making solo webapp development possible",
            "description": "Building modern web apps without the complexity...",
            "created_at": "2024-01-20T10:00:00",
            "views": 142,
            "tags": ["python", "web-development", "tutorial"]
        }
        # ... more blog posts
    ]

A React app would then consume this JSON payload and transform it into HTML:

function BlogCard({ blog }) {
  return (
    <div className="card">
      <h3>{blog.title}</h3>
      <p>{blog.description}</p>
      <div className="metadata">
        <span>{formatDate(blog.created_at)}</span>
        <span>{blog.views} views</span>
      </div>
      <div className="tags">
        {blog.tags.map((tag) => (
          <span key={tag} className="tag">
            {tag}
          </span>
        ))}
      </div>
    </div>
  );
}

But what if you could skip this conversion step and return the HTML directly? That's exactly what FastHTML does:

@rt("/blogposts")
def blogs(auth):
    posts = get_blog_posts()
    return Div(
        *[BlogCard(blog) for blog in posts],
        cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
    )

def BlogCard(blog):
    return Card(
        PostTags(blog.tags),
        H3(blog.title, cls=TextT.bold),
        P(blog.description, cls=TextPresets.muted_sm),
        DivLAligned(
            PostMetrics(blog.content, blog.views),
            cls="justify-between items-center"
        ),
        cls=CardT.hover + CardT.secondary
    )

Notice how conveniently DOM elements map to Python classes. In the blogs router, I query the database and feed the posts directly into BlogCard objects using list comprehension. Since components are just Python functions, my code is far more modular and, in my opinion, easier to maintain.

The Power of HTMX

The secret sauce behind FastHTML is HTMX. If you looked at the code above and wondered, "What happens when these DOM elements are returned from a router?"—the answer is HTMX. This library allows DOM elements to make HTTP requests to these endpoints and decide how to handle the returned data (e.g., replacing elements, adding children, etc.).


I've also noticed that all my state management naturally ends up in backend code, making it much easier to track. React often tripped me up when state became too complex to handle effectively.

Styling with MonsterUI

One of the biggest challenges in web development is creating a polished, professional-looking UI. MonsterUI solves this by providing a comprehensive set of pre-styled components that work seamlessly with FastHTML.


Instead of wrestling with CSS classes and flexbox layouts, you can focus on composition. Here's a simple example of creating a card with MonsterUI:

def UserProfile(user):
    return Card(
        DivLAligned(
            Avatar(user.image),
            H3(user.name, cls=TextT.bold),
            cls="gap-4"
        ),
        P(user.bio, cls=TextPresets.muted_sm),
        cls=CardT.hover
    )

Notice how the styling is declarative and semantic. Instead of remembering CSS class combinations, you use preset styles like CardT.hover or TextPresets.muted_sm. These presets are carefully designed to work together, ensuring consistent spacing, typography, and interactions across your application.


MonsterUI also handles responsive design automatically. Components like Grid and DivLAligned adapt to different screen sizes without requiring complex media queries. This means you can build mobile-friendly layouts with minimal effort.

Truly Dynamic Content

One thing I love about FastHTML and MonsterUI is that I can create a blog without any limitations. I can publish text, but I can also embed whatever DOM elements I want. Here’s an example I made just for this post: if you log in to my site with GitHub, you’ll be able to see a GitHub insights component customized to your profile!


A traditional blogging platform would never allow that! The opportunities are limitless—code playgrounds, real-time dashboards, recommendation systems—whatever you can dream up. This is another reason I’m so excited about these libraries. In less time, I can do significantly more than I ever could with pure blogging frameworks.

GitHub Insights

Log in with GitHub to see your personalized stats!

Resources and Getting Started

My entire personal site is on GitHub if you want a working example:


github.com/erikgaas/erikg

1. Explore MonsterUI Components

Start with MonsterUI. There you will get a lot of inspiration for what is possible with a very small amount of code. The styling is based on FrankenUI, DaisyUI, and TailwindCSS, so it is all based on state of the art technology.

2. Learn FastHTML Basics

Check out the FastHTML documentation. Don't forget to explore the examples. There is even an additional separate repo with advanced features. One really important tip is to take these examples and add them as context to your favorite AI assistant. Just be cautious of hallucinations.

3. Understand HTMX

HTMX powers FastHTML's seamless updates. While you don't need to be an HTMX expert to use FastHTML, understanding its principles will help you build more sophisticated applications.


I really enjoyed watching this conversation between FastHTML creator Jeremy Howard and HTMX creator Carson Gross. For an even deeper dive, check out Hypermedia Systems to understand the underlying philosophy.

Get in Touch

© 2025 Erik Gaasedelen. All rights reserved.