<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Przemysław Kępka</title>
    <description>The latest articles on DEV Community by Przemysław Kępka (@schamack).</description>
    <link>https://dev.to/schamack</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3794522%2Fa9be35b1-5d42-4dc0-b5ce-91a8c0348cc7.png</url>
      <title>DEV Community: Przemysław Kępka</title>
      <link>https://dev.to/schamack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/schamack"/>
    <language>en</language>
    <item>
      <title>How to Choose a Tech Stack in 2026</title>
      <dc:creator>Przemysław Kępka</dc:creator>
      <pubDate>Sat, 04 Jul 2026 07:41:27 +0000</pubDate>
      <link>https://dev.to/tekyous/how-to-choose-a-tech-stack-in-2026-2cek</link>
      <guid>https://dev.to/tekyous/how-to-choose-a-tech-stack-in-2026-2cek</guid>
      <description>&lt;p&gt;Back in the day, so basically in "pre-AI" times, hence not so long ago, if you would ask someone what's their tech stack, the answer would most likely be a list of frameworks, programming languages, maybe no-code tools, but in general it would come down to a handful of tools.&lt;/p&gt;

&lt;p&gt;Fast forward only 4 years, and the answer more often is:&lt;br&gt;
"&lt;em&gt;My tech stack is Claude Code&lt;/em&gt;".&lt;br&gt;
"&lt;em&gt;My tech stack is Codex&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;That's quite a different reality, but not necessarily a simpler one.&lt;br&gt;
The agentic tools like &lt;strong&gt;Claude Code&lt;/strong&gt; and &lt;strong&gt;Codex&lt;/strong&gt; are not replacing all the other tools (at least not yet), and the AI builders like &lt;strong&gt;Lovable&lt;/strong&gt; and &lt;strong&gt;Replit&lt;/strong&gt; are still generating code in the exact same frameworks in which code was previously written manually.&lt;/p&gt;

&lt;p&gt;So how to choose a tech stack in 2026?&lt;br&gt;
Let's tackle this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a tech stack actually is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A tech stack is the set of technologies that make up a software project&lt;/strong&gt;: the frontend framework, the backend runtime, the database, the infrastructure layer, and everything in between.&lt;/p&gt;

&lt;p&gt;This is described in detail in the &lt;a href="https://tekyous.dev/learn/what-is-a-tech-stack" rel="noopener noreferrer"&gt;What is a Tech Stack&lt;/a&gt; article, where we also distinguish tech stack types: project tech stack, developer tech stack, and infrastructure tech stack. These types naturally blend together, and they impact each other. Most of the new tools, like AI coding assistants, are part of a developer tech stack, but they fundamentally change how the projects themselves are being created.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-assisted coding tools: they work &lt;em&gt;within&lt;/em&gt; your stack
&lt;/h2&gt;

&lt;p&gt;AI-assisted coding tools, including the ones based on traditional IDEs — like &lt;strong&gt;Cursor&lt;/strong&gt; or &lt;strong&gt;Windsurf&lt;/strong&gt; — or agentic tools — like Claude Code, Codex or &lt;strong&gt;Warp&lt;/strong&gt; — are tools that help you work inside a stack. They write code, explain errors, suggest refactors, and increasingly manage multi-step tasks in your terminal. They're very good at what they do. Or maybe we should say:&lt;br&gt;
&lt;strong&gt;they are as good as you prompt them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But they work &lt;em&gt;on&lt;/em&gt; your stack, not instead of it. Claude Code still runs inside a &lt;strong&gt;FastAPI&lt;/strong&gt; project or a &lt;strong&gt;Django&lt;/strong&gt; project. It generates SQL against your database schema. It writes Dockerfiles for your infrastructure. When someone says "my stack is Claude Code," what they usually mean is that Claude Code handles most of the typing. Underneath that, there's still a real stack with real technology decisions.&lt;/p&gt;

&lt;p&gt;This is totally different than using no-code tools like &lt;strong&gt;Bubble&lt;/strong&gt;, established in the industry for many years, where there's an extra abstraction layer. There's of course code underneath, but we don't touch it at all. We interact with the interface, which basically integrates everything for us.&lt;/p&gt;

&lt;p&gt;With AI-assisted coding tools, we don't replace programming languages and frameworks with another tool. We rather add tools that work beside them. &lt;strong&gt;All the choices (and mistakes) are still here to make.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI builders: they &lt;em&gt;do&lt;/em&gt; choose the stack for you
&lt;/h2&gt;

&lt;p&gt;AI builders — &lt;strong&gt;Lovable&lt;/strong&gt;, &lt;strong&gt;Bolt.new&lt;/strong&gt;, &lt;strong&gt;v0&lt;/strong&gt;, &lt;strong&gt;Replit Agent&lt;/strong&gt; — sit in a different category. These tools generate entire apps from a natural language prompt. You describe what you want, and you get a working prototype.&lt;/p&gt;

&lt;p&gt;In theory, with agentic tools we can do the same: you describe what you want, and Claude Code will generate the whole app for you.&lt;br&gt;
But the difference is, the AI builders are &lt;strong&gt;one integrated environment&lt;/strong&gt;. They wire up the database for you, they let you host the app instantly on their server, and you connect your own domain as well.&lt;br&gt;
With AI-assisted coding tools we have to do all of it ourselves. Of course, there are integrations, APIs and MCP servers, but AI builders are doing way more out-of-the-box.&lt;/p&gt;

&lt;p&gt;And most of the time (with some exceptions), they will choose the tech stack for us as well, and we might not have an impact at all on selecting the technologies. &lt;strong&gt;But predefined choices by the AI builder might actually be mistaken ones for our use case&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the choices feel harder than ever
&lt;/h2&gt;

&lt;p&gt;Here's what actually happened in the last few years: &lt;strong&gt;AI tools removed the skill barrier to using a wider range of technologies&lt;/strong&gt;. We can forget about a learning curve. We don't have to care about a tool's market share. We just prompt, and we get the code (or the app).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building software has never been as accessible as it is now&lt;/strong&gt;. Whether that's a good or bad thing is not the debate here.&lt;br&gt;
But one thing is certain: even though creating software is more widespread, &lt;strong&gt;we haven't eliminated (at least not yet) the need to make decisions about the underlying technologies of the software we're making&lt;/strong&gt;.&lt;br&gt;
So all the technical debates we were having before, we still have them now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;frontend and backend frameworks&lt;/strong&gt; should we use? Or do we actually need a backend framework at all?&lt;/li&gt;
&lt;li&gt;What type of &lt;strong&gt;database&lt;/strong&gt; should we go for?&lt;/li&gt;
&lt;li&gt;What about &lt;strong&gt;hosting&lt;/strong&gt;? Cloud or VPS?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;? Will the app be able to handle increasing workload?
...and the list could go on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI might attempt to answer all of this. And it might be very successful. But it's based on us. On the context that we provide, on the guidelines that we set up, and the choices that we make beforehand.&lt;/p&gt;

&lt;p&gt;And the list of potential issues has extended as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If we don't select the tech stack beforehand, &lt;strong&gt;the AI choice might be far from ideal&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If we use an AI builder with a predefined tech stack without verifying if that's a good choice for our use case, we potentially &lt;strong&gt;set ourselves up for a big headache&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;We also have to choose the right AI tools for us, and &lt;strong&gt;the number of available tools is growing literally every day&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So things have been simplified and made more complicated at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to think through it
&lt;/h2&gt;

&lt;p&gt;The stack decision still comes down to the same underlying questions, regardless of whether AI is doing the implementation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What type of project is this?&lt;/strong&gt; A web app, a data pipeline, a browser extension, a mobile app, and an automation workflow have different natural stacks. The tool ecosystem for each project type doesn't overlap much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is building and maintaining it?&lt;/strong&gt; A solo developer with a Python background, a team with mixed experience, or a non-technical founder using an AI builder are different starting points with different realistic options. The "best" stack for any project is partly a function of who's operating it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the operational constraints?&lt;/strong&gt; Self-hosted vs managed, cheap vs scalable, relational vs document, type-safe vs flexible — these are questions with real answers that depend on the specific project, not on general best practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long will it live?&lt;/strong&gt; A weekend project has different needs than a platform you'll operate for three years. AI builders are the right call for the former; they introduce meaningful technical debt for the latter.&lt;/p&gt;

&lt;p&gt;You don't have to be an expert in the framework in which the project will be built. Well, potentially you don't even have to know the syntax at all if AI will do the coding. But you should know how this framework works, and why it's a good fit.&lt;/p&gt;

&lt;p&gt;So knowing every line of code in your codebase might no longer be necessary. But &lt;strong&gt;understanding and controlling your tech stack&lt;/strong&gt; is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Tekyous fits in
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tekyous.dev" rel="noopener noreferrer"&gt;Tekyous&lt;/a&gt; is an interactive tech stack builder designed to make the whole selection process more structured.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://tekyous.dev/builder" rel="noopener noreferrer"&gt;Builder&lt;/a&gt;, you can start from a questionnaire — describe your project type, technical level, and language preferences — and get a curated starting stack with a rationale for each tool and options to swap alternatives. Or open the sandbox and drag tools onto a canvas, where the platform gives you real-time hints on compatibility, missing dependencies, and pairings as you build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jwhwdk9ji0f3b7zspxn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jwhwdk9ji0f3b7zspxn.webp" alt="Tekyous Builder canvas" width="799" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The catalogue covers 150+ tools across web development, data tooling, automation, infrastructure, and AI tooling. Recommendations are rule-based and deterministic: not AI-generated suggestions, but data on how tools actually relate to each other — pairings, conflicts, and dependencies built into the database.&lt;/p&gt;

&lt;p&gt;Whether you're using Claude Code to write the implementation, Lovable to prototype it, or writing everything yourself — the underlying technology decisions still matter. Tekyous is the place to work them out before you're committed.&lt;/p&gt;

</description>
      <category>techstack</category>
      <category>development</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>From Streamlit to Flask: When and Why to Make the Jump</title>
      <dc:creator>Przemysław Kępka</dc:creator>
      <pubDate>Mon, 02 Mar 2026 14:53:24 +0000</pubDate>
      <link>https://dev.to/schamack/from-streamlit-to-flask-when-and-why-to-make-the-jump-4fkp</link>
      <guid>https://dev.to/schamack/from-streamlit-to-flask-when-and-why-to-make-the-jump-4fkp</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://przemek-kepka.com/blog/streamlit-to-flask-migration/?utm_source=devto&amp;amp;utm_medium=cross-post" rel="noopener noreferrer"&gt;my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Creating apps in &lt;strong&gt;Streamlit&lt;/strong&gt; feels like a &lt;strong&gt;natural extension for data work in Python&lt;/strong&gt;. You've got your dataframes and charts, possibly already visualized in Jupyter Notebook. You decide to share your work as a web app, fire up Streamlit and...&lt;strong&gt;everything is so smooth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You're still in your known realm, operating on data. One method call and your table is displayed. Two more lines and you have a filter or a clickable button.&lt;/p&gt;

&lt;p&gt;It's less than a few hours and you've built a fully-fledged app. Everything is great, very idyllic...&lt;strong&gt;until it isn't&lt;/strong&gt;. You start &lt;strong&gt;hitting a wall&lt;/strong&gt;, you're literally &lt;strong&gt;battling the framework&lt;/strong&gt;, trying to &lt;strong&gt;squeeze out more than it's meant to offer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A fantasy scenario? Not necessarily. This can be a reality of developing Streamlit applications. But it doesn't have to be a harsh reality.&lt;br&gt;
Streamlit can be a fantastic tool when you &lt;strong&gt;embrace its strengths&lt;/strong&gt;, while being &lt;strong&gt;aware of its limitations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So in this guide we will tackle the problem of Streamlit not being the best fit anymore. The line between when it's shining and when it's underperforming can be blurry, so we will cover the process from the very beginning until the end - a successful migration to another tool.&lt;/p&gt;

&lt;p&gt;And we approach it from a practical perspective - I do have a couple of Streamlit applications under my belt, and with the last one, &lt;a href="https://przemek-kepka.com/projects/cm-rentals/?utm_source=devto&amp;amp;utm_medium=cross-post" rel="noopener noreferrer"&gt;CM Rentals&lt;/a&gt;, I went through exactly this migration path to Flask.&lt;/p&gt;
&lt;h2&gt;
  
  
  What makes Streamlit unique
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Traditional web development&lt;/strong&gt; separates the &lt;strong&gt;backend&lt;/strong&gt; (application logic) and &lt;strong&gt;frontend&lt;/strong&gt; (user interface). Frontend means HTML, CSS, JavaScript, possibly React or Vue.js, while backend can involve even more languages and frameworks.&lt;/p&gt;

&lt;p&gt;That's potentially &lt;strong&gt;a lot of technologies&lt;/strong&gt;. Meanwhile, in the data world, dashboards often weren't enough - &lt;strong&gt;data professionals&lt;/strong&gt; were expected to &lt;strong&gt;create web applications&lt;/strong&gt;. But that meant a &lt;strong&gt;technology mismatch&lt;/strong&gt;: frontend skills aren't something data professionals typically have.&lt;/p&gt;

&lt;p&gt;And that's where &lt;strong&gt;Streamlit comes in clutch&lt;/strong&gt;. It lets you build the entire app &lt;strong&gt;in one language&lt;/strong&gt; - Python - abstracting away all the HTML, CSS and JavaScript complexity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl58uj4mxqylj0ib8bxqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl58uj4mxqylj0ib8bxqi.png" alt="App architecture diagram" width="799" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;ease of development&lt;/strong&gt; comes at the cost of many &lt;strong&gt;compromises&lt;/strong&gt;, as we need some serious complexity under the hood to make it happen.&lt;/p&gt;

&lt;p&gt;What we can achieve "traditionally" might not be possible in Streamlit at all, or will turn out to be overly complex. A crucial part of Streamlit is its &lt;strong&gt;reactivity&lt;/strong&gt; - every interaction with the app, like clicking a button, means that the code for the whole app will be re-run. This introduces concepts of caching and session state management, which can quickly turn simple code into a real headache.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Streamlit Works Great
&lt;/h2&gt;

&lt;p&gt;Before diving into limitations, let's acknowledge where Streamlit excels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rapid prototyping&lt;/strong&gt; - Get a working app in hours, with code you actually understand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data-focused applications&lt;/strong&gt; - Built-in support for charts, tables, and data manipulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML/AI demos&lt;/strong&gt; - And the data focus extends to ML and AI, with built-in functions for features like chatbots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal tools&lt;/strong&gt; - All users get the link to the app directly, so you don't need to worry about search engines or organic discovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-page applications&lt;/strong&gt; - When you need one view with interactive widgets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utility first, appearance second&lt;/strong&gt; - You focus on the app being functional, not on customizing the appearance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Signs You've Outgrown Streamlit
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Maintaining Robust App State
&lt;/h3&gt;

&lt;p&gt;Streamlit's interactivity is a blessing and a curse at the same time. A &lt;strong&gt;full app re-run at every interaction&lt;/strong&gt; means that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're loading a table from a database, Streamlit will attempt to &lt;strong&gt;load it again anytime you click something&lt;/strong&gt; in the app&lt;/li&gt;
&lt;li&gt;If you're displaying a message after clicking a button and then you use another widget, like filter, the button message will &lt;strong&gt;disappear unless retained explicitly&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Streamlit of course provides measures to prevent this and maintain the app state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; for saving the output of a function, meaning that effectively we can just load our dataset once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session state&lt;/strong&gt; for retaining the state of the app, meaning that what we've clicked once is here to stay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit_rerun_cycle.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit_rerun_cycle.webp" alt="Streamlit rerun cycle" width="799" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And while caching might be a little bit easier to harness, session state might be a real hassle if we just want to retain too much.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Performance
&lt;/h3&gt;

&lt;p&gt;Even if somehow we have managed to have immaculate caching and session state, Streamlit still needs to do &lt;strong&gt;very heavy lifting in the background&lt;/strong&gt; to render our Python-only code with a local web server and the frontend.&lt;/p&gt;

&lt;p&gt;So no matter how much optimization we implement in our code, Streamlit is still an &lt;strong&gt;extra abstraction layer&lt;/strong&gt;, and a real performance boost might be possible only by...not using Streamlit.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. SEO Limitations
&lt;/h3&gt;

&lt;p&gt;Streamlit apps are essentially &lt;strong&gt;single-page applications rendered client-side&lt;/strong&gt;. There's &lt;strong&gt;limited control&lt;/strong&gt; over meta tags, URLs, and page structure, so search engines struggle to index the content properly.&lt;/p&gt;

&lt;p&gt;So if our app goes 'public', there's a good chance it will struggle to climb to that #1 position in Google search. &lt;/p&gt;
&lt;h3&gt;
  
  
  4. Dynamic pages
&lt;/h3&gt;

&lt;p&gt;Streamlit allows multipage apps, but it's still a &lt;strong&gt;single-page application&lt;/strong&gt; at its core.&lt;/p&gt;

&lt;p&gt;For CM Rentals, we needed dynamic pages for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt; - dedicated pages for each property with all its information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt; - each page indexed separately by Google, directly discoverable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wanted URLs like &lt;code&gt;https://cm-rentals.com/listing/cosy-apartment&lt;/code&gt; - but this is effectively impossible in Streamlit, as each "page" must be declared explicitly.&lt;/p&gt;

&lt;p&gt;However, in our case &lt;strong&gt;dynamic&lt;/strong&gt; is the keyword - properties come from a database and change over time, so we can't hardcode them. The app should generate URLs automatically.&lt;/p&gt;

&lt;p&gt;The best Streamlit can do for dynamic content is hash-based sections like &lt;code&gt;https://cmrentals.tojest.dev/#cosy-apartment&lt;/code&gt; - nowhere near what we needed.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Layout Constraints
&lt;/h3&gt;

&lt;p&gt;Streamlit comes with a &lt;strong&gt;pre-designed layout&lt;/strong&gt;. Fully functional and looks good out of the box. But if you want to go more custom, then it can be &lt;strong&gt;very cumbersome&lt;/strong&gt; to make it your own.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Mobile capabilities
&lt;/h3&gt;

&lt;p&gt;Streamlit has no &lt;strong&gt;built-in mobile features&lt;/strong&gt;. External libraries can help retrieve screen size and such, so we can adjust the appearance at least a little bit. But it's still &lt;strong&gt;far from perfect&lt;/strong&gt; - the mobile experience won't match desktop.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next? The Migration Path
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Identifying bottlenecks
&lt;/h3&gt;

&lt;p&gt;The Streamlit app is working, it has received positive feedback, why would we change anything? That's a very valid point, and if we consider the project as finished then we could just end it here.&lt;/p&gt;

&lt;p&gt;But if we continue developing it, we need to &lt;strong&gt;identify what's working and what's not&lt;/strong&gt;, weighing the costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staying with Streamlit&lt;/strong&gt; - potentially struggling with (or preventing) app growth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating&lt;/strong&gt; - extra time now, but potential time savings and smoother growth later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We got a functional app in no time. But going forward, the pain points were becoming clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic pages&lt;/strong&gt; - we needed dedicated pages for each property, impossible in Streamlit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt; - the app was public, and Streamlit doesn't shine here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout &amp;amp; mobile&lt;/strong&gt; - the default Streamlit look wasn't cutting it for a public-facing app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; - visible loading times that would require compromising code simplicity to fix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We outgrew Streamlit in all the areas that matter for a &lt;strong&gt;public-facing web application&lt;/strong&gt;. For an internal tool, most of these wouldn't be concerns.&lt;/p&gt;

&lt;p&gt;We were also &lt;strong&gt;outside data-centric&lt;/strong&gt; app boundaries - just dozens of records, no heavy computation. Simply retrieve and display. So besides the functional layer, the importance of appearance was actually knocking here.&lt;/p&gt;
&lt;h3&gt;
  
  
  Redefining the requirements
&lt;/h3&gt;

&lt;p&gt;Before exploring options, let's reconsider our actual requirements.&lt;/p&gt;

&lt;p&gt;Usage of Streamlit imposes some rules on us. It's not only the functional and visual constraints, but also the reactivity. App reload after every interaction is actually the highest degree of interactivity possible. But do we need that much? The answer is - no, not at all.&lt;/p&gt;

&lt;p&gt;Our requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;strong&gt;all features&lt;/strong&gt; - map display, property table, filtering&lt;/li&gt;
&lt;li&gt;Retain the &lt;strong&gt;database schema&lt;/strong&gt; - no point reorganizing a working data layer&lt;/li&gt;
&lt;li&gt;Keep &lt;strong&gt;external integrations&lt;/strong&gt; - Supabase, Folium&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;dedicated property pages&lt;/strong&gt; - for SEO and UX&lt;/li&gt;
&lt;li&gt;Improve &lt;strong&gt;layout and mobile responsiveness&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reactivity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load all data&lt;/strong&gt; on page load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters don't need to be instant&lt;/strong&gt; - clicking "Apply" button is perfectly acceptable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conclusion: &lt;strong&gt;we don't need a highly interactive application&lt;/strong&gt;. A static website with pre-loaded content will do. This aligns with our SEO goals and should make the code simpler.&lt;/p&gt;
&lt;h3&gt;
  
  
  Choosing the new tech stack
&lt;/h3&gt;

&lt;p&gt;So the decision is made - farewell Streamlit. But the abundance of web technologies can be staggering and intimidating at the same time.&lt;/p&gt;
&lt;h4&gt;
  
  
  Tech-stack prerequisites
&lt;/h4&gt;

&lt;p&gt;We need constraints. Starting from our Python background:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stay with Python&lt;/strong&gt; - leveraging what we know, not jumping to distinct (for us) technologies like PHP or .Net&lt;/li&gt;
&lt;li&gt;Accept learning &lt;strong&gt;some new technologies&lt;/strong&gt; if needed&lt;/li&gt;
&lt;li&gt;Avoid frameworks with &lt;strong&gt;steep learning curves&lt;/strong&gt; - our app isn't that complex&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Python-based data frameworks
&lt;/h4&gt;

&lt;p&gt;Streamlit is actually not one of a kind. There are more tools boasting to be &lt;strong&gt;all-in-one&lt;/strong&gt;, allowing to create web apps only with Python, focusing on data. &lt;strong&gt;Dash&lt;/strong&gt;, &lt;strong&gt;Panel&lt;/strong&gt;, &lt;strong&gt;NiceGUI&lt;/strong&gt;, &lt;strong&gt;Reflex&lt;/strong&gt;, the list can go on. Usually they will avoid this problematic full re-run, and will potentially offer bigger capabilities as well.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;switching to another data framework is unlikely to solve our core problems&lt;/strong&gt;. While some (like Dash) offer better URL routing, they still share fundamental constraints on SEO and layout control.&lt;/p&gt;
&lt;h4&gt;
  
  
  Full-stack frameworks
&lt;/h4&gt;

&lt;p&gt;So we enter the full-stack world. And that world is really vast, with three main setups available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate Backend + Frontend&lt;/strong&gt; - Most complex, most capable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend + HTML templates&lt;/strong&gt; - Server-Side Rendering (SSR), simpler if high interactivity isn't needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend + Backend-as-a-Service&lt;/strong&gt; - JavaScript-heavy, highly interactive, relying on Client-Side Rendering (CSR)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Decision process
&lt;/h4&gt;

&lt;p&gt;Our requirements narrow this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross out Frontend + BaaS&lt;/strong&gt; - doesn't favour SEO, JavaScript-heavy, can't leverage Python&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross out full separate stack&lt;/strong&gt; - overengineering for our needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That leaves &lt;strong&gt;Backend + Templates&lt;/strong&gt;. Within Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flask&lt;/strong&gt; - simplest, most lightweight&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django&lt;/strong&gt; - "batteries included", great for larger projects, potentially too heavy here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; - excellent for APIs, but server-rendered pages aren't its strength&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flask&lt;/strong&gt; was the natural choice - lightweight, flexible, and Jinja2 templates give us the server-side rendering we need for SEO.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementation plan
&lt;/h3&gt;

&lt;p&gt;We have our winner. But how to proceed with the migration now?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the Streamlit code as reference&lt;/strong&gt; - do not erase it. Create a &lt;strong&gt;new git repository&lt;/strong&gt; - having both codebases helps understand the Flask code by comparing it with the original.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choose your coding approach.&lt;/strong&gt; Write it yourself, or leverage AI - there's never been a better time to say: &lt;em&gt;"I have my app in technology X, please rewrite it in technology Y"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Product Requirements Document (PRD).&lt;/strong&gt; We already have our redefined requirements. Listing features, new additions, and layout expectations significantly decreases disappointment with the first version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Iterate feature by feature.&lt;/strong&gt; Work on one feature at a time, ensuring it works before moving on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test throughout.&lt;/strong&gt; Don't forget mobile testing - UX and mobile-friendliness are key aspects here.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Flask vs Streamlit - Core Differences
&lt;/h2&gt;

&lt;p&gt;So what does it actually look like to go from Streamlit to Flask? The fundamental difference is that &lt;strong&gt;Flask separates what Streamlit combines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In Streamlit, your Python code is your app - the logic, the layout, the interactivity, all in one place. In Flask, these concerns are split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routes&lt;/strong&gt; (Python) - define what happens when someone visits a URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; (HTML/Jinja2) - define what the page looks like&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static files&lt;/strong&gt; (CSS/JS) - handle styling and interactivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means more files and more structure, but also &lt;strong&gt;full control&lt;/strong&gt;. Every HTML element, every CSS rule, every URL is yours to define.&lt;/p&gt;

&lt;p&gt;To illustrate with a simple example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streamlit&lt;/strong&gt; - display a property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flask&lt;/strong&gt; - the same thing, but separated into a route and a template:&lt;/p&gt;

&lt;p&gt;Route (Python):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/listing/&amp;lt;slug&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;property&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_property_by_slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;listing.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template (HTML/Jinja2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ property.title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ property.description }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ property.price }}/month&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More code? Yes. But notice what we got: a &lt;strong&gt;clean URL&lt;/strong&gt; (&lt;code&gt;/listing/cosy-apartment&lt;/code&gt;), &lt;strong&gt;full HTML control&lt;/strong&gt;, and the ability to add &lt;strong&gt;meta tags, structured data, or any CSS&lt;/strong&gt; we want. These are the things that weren't possible in Streamlit.&lt;/p&gt;

&lt;p&gt;For filtering? Standard &lt;strong&gt;HTML forms&lt;/strong&gt; with a GET request do the job. Simple, reliable, no JavaScript framework required.&lt;/p&gt;

&lt;p&gt;Streamlit's built-in widgets are arguably easier to set up. But Flask gives you &lt;strong&gt;full control&lt;/strong&gt; over how filters look, how URLs change, and how results display.&lt;/p&gt;

&lt;p&gt;Project structures also differ - Flask has more folders and files, but both remain easy to follow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streamlit project structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cm-rentals-streamlit/
├── app.py                  # Main entry point - all UI code here
├── pg/
│   ├── form.py             # Form page logic
│   └── map.py              # Map page logic
├── property_map/
│   ├── db.py               # Database queries
│   └── map_utils.py        # Map utilities
├── static/
│   └── robots.txt
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flask project structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cm-rentals-flask/
├── flask_app/
│   ├── __init__.py         # App factory
│   ├── config.py           # Configuration
│   ├── constants.py        # Constants
│   ├── errors.py           # Error handlers
│   ├── views.py            # Route definitions
│   ├── services/
│   │   └── properties.py   # Business logic
│   └── utils/
│       └── map_builder.py  # Map utilities
├── templates/              # HTML templates (Jinja2)
│   ├── base.html           # Base layout
│   ├── index.html          # Homepage
│   ├── listing_detail.html # Property page
│   ├── privacy.html
│   └── errors/
│       ├── 404.html
│       └── 500.html
├── static/
│   └── css/
│       └── main.css        # Custom styles
├── wsgi.py                 # WSGI entry point
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streamlit: &lt;strong&gt;5 Python files&lt;/strong&gt;. Flask: &lt;strong&gt;10 Python files&lt;/strong&gt; plus &lt;strong&gt;6 HTML templates&lt;/strong&gt; and a &lt;strong&gt;CSS file&lt;/strong&gt;. More files, but more control. With Flask we even have custom error pages - a "real web app" in every aspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;After the migration, the differences were clear:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Streamlit&lt;/th&gt;
&lt;th&gt;Flask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to usable content&lt;/td&gt;
&lt;td&gt;~3-4s&lt;/td&gt;
&lt;td&gt;~1.7s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lighthouse Performance&lt;/td&gt;
&lt;td&gt;Couldn't measure*&lt;/td&gt;
&lt;td&gt;68**&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google indexed pages&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile usability&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout control&lt;/td&gt;
&lt;td&gt;Constrained&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Lighthouse timed out waiting for Streamlit to render content (NO_FCP error). Streamlit's HTML loads fast, but it's an empty shell - users watch the "running man" spinner while JavaScript renders the actual content. Flask sends ready-to-display HTML.&lt;/p&gt;

&lt;p&gt;**Flask's 68 Lighthouse score isn't very impressive - image optimization and other tweaks could push it higher. But for a side project, "measurable and indexable" was already a big step up from "couldn't measure at all."&lt;/p&gt;

&lt;p&gt;The biggest win was &lt;strong&gt;SEO&lt;/strong&gt;: going from one indexable page to &lt;strong&gt;over 50 individual property pages&lt;/strong&gt;, each discoverable through Google search. If Google's tools can't measure your app, search engines will struggle to index it.&lt;/p&gt;

&lt;p&gt;And the &lt;strong&gt;layout freedom&lt;/strong&gt; - being able to design the pages exactly how I wanted, without fighting Streamlit's constraints - was honestly a relief.&lt;/p&gt;

&lt;p&gt;But does it actually look better?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit-interface.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit-interface.webp" alt="Streamlit interface" width="799" height="613"&gt;&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fflask-interface.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fflask-interface.webp" alt="Flask interface" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The UI is &lt;strong&gt;much cleaner&lt;/strong&gt; now, with traditional navigation headers making the site easy to browse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit-listings-table.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fstreamlit-listings-table.webp" alt="Streamlit table" width="798" height="228"&gt;&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fflask-listings-table.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Fflask-listings-table.webp" alt="Flask table" width="799" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table is also much more appealing. We could have tried to make it more sophisticated in Streamlit as well, but it would require direct HTML - not straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Each project migration will be different
&lt;/h3&gt;

&lt;p&gt;There's &lt;strong&gt;no default rule&lt;/strong&gt; that Streamlit-to-Flask migration is always the right choice.&lt;/p&gt;

&lt;p&gt;We did a &lt;strong&gt;thorough assessment&lt;/strong&gt; leading to Flask. Different projects with different goals might need React/Vue.js (for interactivity) or Django (for complex features like user accounts, comments, ratings).&lt;/p&gt;

&lt;p&gt;So while our decision process was as follows, it might still vary heavily for other projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Ftech_stack_decision.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprzemek-kepka.com%2Fimages%2Fblog%2Fstreamlit-to-flask-migration%2Ftech_stack_decision.webp" alt="Tech stack decision process" width="625" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don't Over-Engineer
&lt;/h3&gt;

&lt;p&gt;Web dev world is not only vast, but also &lt;strong&gt;very volatile&lt;/strong&gt;, changing rapidly. There will always be some new frameworks, shiny tools and most popular choices. And that puts us at risk of going down a rabbit hole, choosing overly complicated tools and &lt;strong&gt;not leveraging our current skills&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Control the whole migration and development process
&lt;/h3&gt;

&lt;p&gt;Even when delegating coding to AI, you must &lt;strong&gt;stay in the loop&lt;/strong&gt; and understand your codebase.&lt;/p&gt;

&lt;p&gt;Without clear requirements and the right AI tool (Claude Code in this case), we might have ended up with an over-engineered JavaScript framework. Instead, we made &lt;strong&gt;conscious choices from the beginning&lt;/strong&gt;, maintaining control while leveraging LLMs for code creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Database is the backbone, no matter the technologies used for the app itself
&lt;/h3&gt;

&lt;p&gt;Having a data background, we understand the importance of &lt;strong&gt;solid data foundations&lt;/strong&gt; from the start. Outside data world, this often gets neglected, leading to serious issues in syncing the application itself and the database.&lt;/p&gt;

&lt;p&gt;With the right approach, our data layer &lt;strong&gt;perfectly integrates with any technology&lt;/strong&gt;. Streamlit or Flask - same database, same tables. Only the app code differs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Streamlit Skills Transfer
&lt;/h3&gt;

&lt;p&gt;Good news: &lt;strong&gt;you're not starting from zero&lt;/strong&gt;. Session management, caching, database queries - the mental models carry over, just implemented differently. Same &lt;strong&gt;Python ecosystem&lt;/strong&gt;, same libraries.&lt;/p&gt;

&lt;p&gt;And having wrestled with Streamlit's session state? You'll appreciate how other frameworks handle it (spoiler: &lt;strong&gt;much simpler&lt;/strong&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Stay with Streamlit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Migration isn't always the answer.&lt;/strong&gt; Before committing to a rewrite, honestly assess if you actually need it. &lt;strong&gt;Stay with Streamlit&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app is &lt;strong&gt;internal-only&lt;/strong&gt; - no SEO concerns, no public users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appearance is secondary&lt;/strong&gt; - the default look is fine for your use case&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;rapid iteration&lt;/strong&gt; over polish - shipping fast matters more than pixel-perfect design&lt;/li&gt;
&lt;li&gt;Your team &lt;strong&gt;knows Python but not web development&lt;/strong&gt; - and learning HTML/CSS/JS isn't on the roadmap&lt;/li&gt;
&lt;li&gt;The app is &lt;strong&gt;data-exploration focused&lt;/strong&gt; - exactly what Streamlit was built for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing wrong with that. Sometimes Streamlit is &lt;strong&gt;exactly the right tool&lt;/strong&gt; - acknowledging that is as important as knowing when to move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The CM Rentals migration was a significant effort. But the result - a &lt;strong&gt;faster, SEO-friendly, mobile-responsive&lt;/strong&gt; application with complete design control - made it worth it.&lt;/p&gt;

&lt;p&gt;The real takeaway isn't "Flask is better than Streamlit." It's that &lt;strong&gt;Streamlit and Flask aren't competitors - they're tools for different stages and different needs&lt;/strong&gt;. Start fast with Streamlit to validate your idea, and migrate when (and if) you outgrow it.&lt;/p&gt;

&lt;p&gt;Facing a similar decision? Check out the &lt;a href="https://przemek-kepka.com/projects/cm-rentals/?utm_source=devto&amp;amp;utm_medium=cross-post" rel="noopener noreferrer"&gt;CM Rentals project page&lt;/a&gt; to see the result, or reach out if you want to discuss specifics.&lt;/p&gt;

</description>
      <category>streamlit</category>
      <category>flask</category>
      <category>webdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
