{"id":"src_u9K1H_A33cAii_6Pon7dE","slug":"htmx","name":"htmx","type":"feed","url":"https://htmx.org/atom.xml","orgId":"org_GXPCJgPaWv0bd3Ql3QWfZ","productId":null,"productSlug":null,"org":{"id":"org_GXPCJgPaWv0bd3Ql3QWfZ","slug":"htmx","name":"htmx"},"isPrimary":false,"isHidden":false,"discovery":"curated","metadata":"{\"feedUrl\":\"https://htmx.org/atom.xml\",\"feedType\":\"atom\",\"feedEtag\":\"W/\\\"2efe62d152e3b9b2fc52fb3b2b9a332c-ssl-df\\\"\",\"feedContentLength\":\"244754\"}","notice":null,"kind":"sdk","stars":null,"starsFetchedAt":null,"releaseCount":84,"releasesLast30Days":2,"avgReleasesPerWeek":0.2,"latestVersion":null,"latestDate":"2026-06-11T00:00:00.000Z","changelogUrl":null,"hasChangelogFile":false,"lastFetchedAt":"2026-06-13T06:00:40.226Z","lastPolledAt":"2026-06-26T14:00:39.944Z","trackingSince":"2020-05-15T00:00:00.000Z","releases":[{"id":"rel_mg0tBIR6CHETbpTmOYTrR","version":null,"type":"feature","title":"The University In The AI Era","summary":"As I mentioned in [“Yes, And”](https://htmx.org/essays/yes-and/), I teach computer science at [Montana State University](https://www.cs.montana.edu).\n...","titleGenerated":null,"titleShort":null,"content":"As I mentioned in [“Yes, And”](https://htmx.org/essays/yes-and/), I teach computer science at [Montana State University](https://www.cs.montana.edu).\n\nIn that earlier essay, I say that computer science is probably still a reasonably good area to study, but that you should also expand your skills beyond “just” computer science to help make yourself more employable in the future.\n\nIn this essay I want to think more about what AI means for universities in general and computer science programs in particular.\n\n*Note: I apologize that this is a longer essay. I have provided a Table of Contents to help you navigate it.*\n\n  \n\n# Table of Contents\n\n- [First: Is The University Still Relevant?](https://htmx.org/essays/universities-and-ai/#first-is-the-university-still-relevant)\n- [Writing Code](https://htmx.org/essays/universities-and-ai/#writing-code)\n- [Signaling Competence in an AI World](https://htmx.org/essays/universities-and-ai/#signaling-competence-in-an-ai-world)\n- [Towards An AI-accepting CS Curriculum](https://htmx.org/essays/universities-and-ai/#towards-an-ai-accepting-cs-curriculum)\n    -   [Current Changes](https://htmx.org/essays/universities-and-ai/#current-changes)\n        -   [Homework Is No Longer A Strong Signal](https://htmx.org/essays/universities-and-ai/#homework-is-no-longer-a-strong-signal)\n        -   [Homework Can Be More Ambitious & Realistic](https://htmx.org/essays/universities-and-ai/#homework-can-be-more-ambitious-realistic)\n        -   [AI is a Great TA](https://htmx.org/essays/universities-and-ai/#ai-is-a-great-ta)\n        -   [The Return of Butt-in-chair, Handwritten Tests](https://htmx.org/essays/universities-and-ai/#the-return-of-butt-in-chair-handwritten-tests)\n        -   [Demos & Visualizations Are Cheap](https://htmx.org/essays/universities-and-ai/#demos-visualizations-are-cheap)\n        -   [Class Content Should Be In Markdown](https://htmx.org/essays/universities-and-ai/#class-content-should-be-in-markdown)\n        -   [Class Analysis & Improvements](https://htmx.org/essays/universities-and-ai/#class-analysis-improvements)\n        -   [Automate *Everything*](https://htmx.org/essays/universities-and-ai/#automate-everything)\n    -   [Upcoming Changes](https://htmx.org/essays/universities-and-ai/#upcoming-changes)\n        -   [Stronger Pseudocode Standards](https://htmx.org/essays/universities-and-ai/#stronger-pseudocode-standards)\n        -   [AI & Non-AI Tracks](https://htmx.org/essays/universities-and-ai/#ai-non-ai-tracks)\n        -   [Open Source Work](https://htmx.org/essays/universities-and-ai/#open-source-work)\n        -   [Clearly, Honestly Communicating The Dangers of AI](https://htmx.org/essays/universities-and-ai/#clearly-honestly-communicating-the-dangers-of-ai)\n    -   [Speculative Changes](https://htmx.org/essays/universities-and-ai/#speculative-changes)\n        -   [The “CS+” Concept](https://htmx.org/essays/universities-and-ai/#the-cs-concept)\n        -   [Network Isolated Computers](https://htmx.org/essays/universities-and-ai/#network-isolated-computers)\n        -   [Interview-Based Grading](https://htmx.org/essays/universities-and-ai/#interview-based-grading)\n- [Conclusion](https://htmx.org/essays/universities-and-ai/#conclusion)\n\n  \n  \n\n# First: Is The University Still Relevant?\n\nAn initial question that many people are asking is: in the era of AI, is the University still relevant?\n\nThis is not a new question. Many people have pointed to famous software industry figures who dropped out of college as proof that a university education isn’t useful in technology. And most people who have worked in Silicon Valley know at least one excellent engineer who either dropped out or simply never went to college.\n\nSo a college degree has never been a hard requirement for a successful career in technology. But, in reality, *most* software engineers have some sort of college under their belt and many of the best developers have studied computer science in their undergraduate education.\n\nThat being said, there is [clearly an emerging crisis](https://www.dailycal.org/news/campus/academics/failing-grades-soar-as-professors-see-greater-ai-usage-dwindling-math-skills-in-uc-berkeley/article_16fad0bf-02cb-4b8c-8d88-888ffd9f8608.html) in Computer Science education that needs to be addressed in order to keep the university relevant in the post-AI world.\n\n# Writing Code\n\nHistorically, many computer science departments have looked at writing code as a secondary skill, to be picked up by students on their own, while the department focuses more on the theoretical foundations of computer science.\n\nSince I was mature enough to have an opinion on the matter, I have viewed this as wrongheaded: I think you need to learn how to write code in order to appreciate those deeper theoretical foundations of computer science. If you can’t code up a linked list or use a hash table effectively, learning about the big-O behavior of them is much more abstract and difficult to grasp.\n\nIronically, in the era of AI, many *professional* environments are *also* starting to look at raw coding somewhat skeptically, sometimes insisting that their own engineers not write code at all, but rather use agents to generate it.\n\nThis approach may work for more experienced seniors, who have already written a lot of code and know what reasonable code looks like, but it puts junior developers in a bind: they don’t have pre-AI experience writing code, and now they are going into environments where no one is writing code.\n\nAs I said in “Yes, And”, you must write the code if you want to develop the ability to read code.\n\nHow is that supposed to happen at companies where nobody is writing the code?\n\nI think this presents an opportunity for Computer Science departments: *we* can be the places where young software engineers write the code. By refocusing our curriculum on practical, code heavy assignments we can give students a safe environment, free of the time pressures and demands of corporate work, to write the code.\n\nThis experience can then put them in position to go into environments that use AI more heavily with the confidence that they know how to code and, because of that, are in a position to read and understand the code necessary for their career.\n\n# Signaling Competence in an AI World\n\nNow, of course, students are famously lazy and famously clever in figuring out how to be lazy. So, many students will use AI to complete many of these code-heavy assignments. They will learn very little or nothing, but will get a good grade because, let’s be honest, AI can perform at or above the level required for most reasonable undergraduate projects.\n\nHere another irony of the AI era becomes evident: Universities are now in a position to signal competence in a way that nearly no other institution can. AI has made online testing pointless. I know this because the last semester I offered online tests (which I like to do because it is convenient for my working students) the testing scores were through the roof.\n\nWhile I feel I am a pretty good teacher, this was clearly a case of AI being used by my students, despite my pleas.\n\nWhen thinking about what I could do about this I realized that we had all the infrastructure for the perfect answer: in person, on paper testing. Universities have large lecture halls, expensive printers, testing centers for people who need additional help, etc.\n\nPreviously, I would have scoffed at this infrastructure as antiquated. But now I see that it puts me in a position to *more accurately establish* the competence of my students in a way that is difficult to game: I offer in-person quizzes, with one page of handwritten notes and no digital equipment, roughly every three weeks of my courses.\n\nOf course students can still cheat, but the quizzes are proctored and now at least they have to work for it.\n\nThis in-person, on-paper testing infrastructure puts universities in a unique position to provide a high signal-to-noise indication of the competence of their students to the outside world.\n\n# Towards An AI-accepting CS Curriculum\n\nWhile I believe that the University CS degree is not only still relevant, but perhaps of *more* value than it was in the pre-AI era, I do think that significant changes need to be made to adapt to the new state of affairs. In this section I will describe what I have done over the last year with my courses, what I plan to do in the near future & then finish with some more speculative changes that I believe would help increase the usefulness of undergraduate CS degrees.\n\n## Current Changes\n\nFirst, let’s start with the changes that I have already made to my courses over the last year to deal with the new AI reality.\n\n### Homework Is No Longer A Strong Signal\n\nAs with take home quizzes, due to the use of AI, homeworks & projects are no longer a strong signal of a students understanding of material.\n\nHomeworks must become for the student’s benefit, opportunities for them to learn the art of writing code, rather than for evaluating their competency.\n\nThis is actually a good thing: homeworks can be more ambitious and the students that want to learn will have more opportunities to write more advanced code.\n\nYes, some (many?) will cheat on assignments, but the good students will have an opportunity to write code in a supportive environment.\n\nTo address this fact, I have reduced the weight of assignments in my classes from 60-80% (I have always had code-heavy classes) down to 50%, and I expect most students will get A’s on most assignments.\n\n### Homework Can Be More Ambitious & Realistic\n\nAnother homework related change that I have made is that my assignments are now more ambitious and realistic. I don’t mean they are much harder.\n\nInsead, what I mean is that I can, with the help of AI, present much larger software systems to my students with better sample data, as a basis for their projects.\n\nThis allows students to see software systems that go beyond “Hello World” levels of complexity and to develop the ability to navigate, read and write code in a larger, more complicated and realistic context.\n\n### AI is a Great TA\n\nAnother thing I have found in the post-AI era is that my office hours traffic has dropped precipitously. I have always done my office hours in the computer lab on campus and, particularly for my compilers class, expected large crowds of students to come in asking for help on projects.\n\nI think, unfortunately, this is most likely due to many students using AI to solve their programming problems.\n\nHowever, there is a more optimistic read here: the students are using AI to better understand the projects and therefore do not need as much one on one help.\n\nWhile I am ambivalent in many ways towards AI, this is an area where AI can significantly improve the university experience for students: with proper use, AI can be a *fantastic* TA. It is infinitely patient, has no other students waiting in line or it’s own classes to attend to and it is usually very competent at undergraduate level concepts in computer science.\n\nThe danger, of course, is that students simply use AI as a code generator to complete assignments and head off to the bars.\n\nTo address this danger, I ship a [`CLAUDE.md/AGENTS.md`](https://gist.github.com/1cg/a6c6f2276a1fe5ee172282580a44a7ac) file in my class repos that directs AI agents to act like a good TA rather than a code generator.\n\nOf course students can modify or delete this file, but there is no system so perfect that no one needs to be good.\n\nStanford University has recently [modified this file for one of their own classes](https://github.com/stanford-cs336/assignment1-basics/blob/main/CLAUDE.md), and I encourage other people and departments to do the same: it is public domain.\n\n### The Return of Butt-in-chair, Handwritten Tests\n\nAs I discussed above, Universities have infrastructure for in-person testing that make them uniquely qualified to assess expertise and competence in the post AI world.\n\nI have switched to all in-person quizzes, roughly every three weeks. The three-week cadence gives enough time to cover a significant amount of material, even if holidays are interspersed in those weeks, while de-escalating each quiz when compared to a traditional midterm/final setup.\n\nI also allow one page of handwritten notes. I do not allow printed notes. The idea here is to force the knowledge through the student’s eye-brain-hand pathway multiple times in order to help reinforce it.\n\nMy students have grumbled about this process, but also admit that it works in helping them learn the material.\n\nMy questions are all written response, never multiple choice. Sometimes I ask for prose, sometimes I ask for pseudocode, sometimes I will provide code and ask students to annotate/explain it, etc. This makes it harder to grade the tests, but also makes it much harder to cheat.\n\nI have found that AI is very good at suggesting questions based on class material for quizzes. I will work with an AI agent based on my class slides (see below) to create appropriate quiz questions and then create a quiz review sheet to help students study for the quiz based on it.\n\nStudents love the review sheet because it helps them focus their studing efforts.\n\nI think that, from a learning perspective, the butt-in-chair quizzes have been the single most positive change I have made to my classes. I now make quizzes 50% of a student’s grade, and my class grading curve has returned to a reasonable shape.\n\n### Demos & Visualizations Are Cheap\n\nAnother adjustment I have had to make is that demos & visualizations are now very cheap to create with AI.\n\nFor a long time I was unhappy with the computer emulators that were available to me to teach my [computer systems](https://www.montana.edu/cope/activity_insight/catalog.php?rubric=CSCI&number=366) class. I wanted a 16-bit computer that struck a balance between the simplicity of something like [The Scott CPU](https://www.youtube.com/playlist?list=PLnAxReCloSeTJc8ZGogzjtCtXl_eE6yzA) and the full complexity of something like [SPIM](https://spimsimulator.sourceforge.net/).\n\nTwo summers ago we spent an entire summer building such a computer, called [The Montana Mini Computer](https://mtmc.cs.montana.edu/) that provided strong visualizations of how low level computing works.\n\nUnfortunately, when I got into a class using it, I realized that the architecture I had picked was too exotic (mixing concepts from MIPS & the JVM) and that students would be better off learning an assembly closer to x86. x86 would be particularly useful later in our security classes.\n\nI was able to work with AI to produce a new MTMC that was much closer to x86 in only a few weeks. It was so successful that I switched to the new version of the MTMC mid-class in fall, and used it exclusively in spring.\n\nAnother visualization that I have created with AI is a JVM emulator that shows how stack frames and the operand stack work together to do computation. It is a visualization that I always wanted, but was unable to create. This was not due to lack of skill on my part (I am a reasonably competent programmer) but just lack of time and energy (I am old.)\n\nSo I have had to reset my thinking on demos & visualizations: If you can think of a demo and describe it, and if you are a reasonably good programmer, you can probably create it.\n\n### Class Content Should Be In Markdown\n\nI have moved all my class slides to Markdown, using a tool called [slidev](https://sli.dev/) and, generally, embraced [Markdown](https://www.markdownguide.org/) for all my class content: SYLLABUS.md, etc. (Previously I was using Google slides for my lectures.)\n\nAll Markdown content is checked in to my class repository that students get.\n\nMoving all my content to Markdown has been tremendously beneficial:\n\n- I can run AI analysis over my slides and look for gaps or inconsistencies\n- Students can run the content through an AI agent to create a more effective TA\n- It is much easier to bulk-update my slides if I make a major change to a class\n\nI have always liked Markdown and, with slidev, I have nice syntax highlighting and access to [Mermaid](https://mermaid.ai/open-source/intro/) for technical diagrams. Or I can use good ol’ ASCII art, which is often very effective.\n\nHaving everything locally in text/Markdown makes it much easier for AI tools to work effectively in my classes. As I mentioned above, AI agents can easily look at my class slides and suggest quiz questions I might ask.\n\n### Class Analysis & Improvements\n\nAnother way I have used AI effectively, again enabled by moving everything to Markdown, is in reviewing my classes at a higher level:\n\n- I can compare my classes with courses offered at other universities to see if there are topics that I am missing\n- I can analyze my classes holistically and ensure that there are coherent threads between them (e.g. stack machines)\n\nWhile this hasn’t revolutionized any of my classes I feel it has been useful in improving them.\n\n### Automate *Everything*\n\nThe final way that I have been using AI to improve my classes is in automating everything possible. I have always had a significant number of scripts that I use for the infrastructure in my classes: an autograder.py that runs the autograding for project checkpoints in CI, etc.\n\nI have become much more aggressive in what I will automate and optimize now.\n\nFor example, I am using [Tampermonkey](https://www.tampermonkey.net/) to make parts of [Canvas LMS](https://www.instructure.com/canvas) easier to work with for my work flows:\n\n- I can paste in a youtube URL, and it will automatically create a link for me in Canvas\n- I can drag and drop files directly from my OS into Canvas\n\nYou can see the tampermonkey script [here](https://gist.github.com/1cg/b5ed907a53eee2faa0bd6d079eeadb17)\n\nI have also created command line scripts for scheduling new Youtube streams, parsing our autograder output into Canvas compatible format, etc.\n\nAt this point, if there is friction somewhere in my class I try to think how I would remove it if I had the time, then consider if an LLM could generate that solution.\n\n## Upcoming Changes\n\nI plan on implementing the following changes in the upcoming semester.\n\n### Stronger Pseudocode Standards\n\nWith on-paper quizzes becoming a standard, it has become clear that I need a strong pseudocode standard for students to use on quizzes.\n\nWe are working on an “executable” pseudocode, [Notch](https://notch.cs.montana.edu), to address this. It is english-like (it is an [xTalk](https://en.wikipedia.org/wiki/HyperTalk) variant) and uses standards from Java, so the students should be able to pick it up easily. We will of course be lenient on syntax when it is used as pseudocode.\n\nI intend to provide a pseudo-code guide that students are allowed to bring to quizzes for reference.\n\nWe will see how it goes.\n\n### AI & Non-AI Tracks\n\nIn many of my classes I will have students give end-of-semester presentations and I often offer a reward for the best in show. In the upcoming year I am splitting these presentations into two tracks: AI & non-AI.\n\nThis will allow students who do not want to use AI to compete with one another and, I hope, encourage more students to not use AI for their projects.\n\nI will stress that I will review the non-AI winner’s code base and, if I sniff any AI, they will be heavily penalized.\n\n### Open Source Work\n\nAI is disrupting Open Source work significantly. It changes the calculus on [build vs buy](https://www.thoughtworks.com/content/dam/thoughtworks/documents/e-book/tw_ebook_build_vs_buy_2022.pdf) dramatically in favor of build. This is made more compelling by people recognizing that dependencies [are liabilities](https://x.com/htmx_org/status/2057205905222246455), especially from a security perspective.\n\nOf course much of the AI model training set consists of open source work and many open source developers are [understandably upset about this](https://opensourceainews.com/ai-is-destroying-open-source-not-even-good-yet/).\n\nChad Whitacre, an open source advocate who I respect tremendously, has decided to [step away from technology entirely](https://openpath.quest/2026/i-am-retiring-from-tech-to-live-offline/) due to the situation.\n\nI do not have any good answers of how to prevent AI models from using open source work for training, nor do I have a good answer for financing open source work in general.\n\nHowever, one possibility that I see is that universities become more explicitly involved in open source work, by forming open source groups. We have done so at [Montana State](https://opensource.cs.montana.edu) to support tools we use in our classes.\n\nUniversities have independent financing that allows them to pursue projects with steady (if unglamorous) levels of financial backing. This also dovetails with the public mission of many universities. Montana State, for example, is a land grant university, founded for “the advancement of agriculture, mechanical arts and military tactics.”\n\nLeaving aside military tactics, open source is one way that public universities can contribute to the public good in a meaningful way.\n\n### Clearly, Honestly Communicating The Dangers of AI\n\nThis upcoming semester I am going to spend more time communicating the dangers of AI to my students. The most obvious short term danger is that [they won’t write the code](https://htmx.org/essays/yes-and/#juniors) and, therefore, will not learn the skills needed to read the code.\n\nThey may get good grades on assignment, but my quizzes will be difficult and, when they enter the real world, they will not be able to work effectively, either with or without AI.\n\nThere are also studies showing that AI is [stultifying](https://www.media.mit.edu/projects/your-brain-on-chatgpt/overview/) and [homogenizes away creativity](https://www.nature.com/articles/s41562-025-02173-x).\n\nI have been telling my students for many years now that they face far, far more temptations to behave poorly than any generation before them. That they face more difficult temptations in a week than their grandparents generation faced in years and perhaps decades.\n\nWith AI we now have another dimension, automated cheating, along which they will need to exercise even greater virtue than previous generations.\n\nI stress to them that I admire the heroism of their generation in resisting these temptations.\n\n(I think older generations would do well to recognize this fact.)\n\n## Speculative Changes\n\nFinally, I want to give a few concepts that I have thought of given an unlimited time & money budget.\n\n### The “CS+” Concept\n\nAn idea proposed by MSU professor [Laura Stanley](https://www.cs.montana.edu/directory/1524633/laura-stanley) is “CS+”: integrating computer science education *plus* other majors in a meaningful way. Studies have shown that this increases the appeal of computer science more broadly to the student body.\n\nI think this is a fantastic idea and will be working with our department to help re-orient our minor towards this concept. My hope is that we can refocus the minor and the first two years of our major program towards practical problem-solving with computers.\n\nPerhaps AI will reduce the number of computer scientists needed in the world (I am skeptical of this claim) but it will certainly not reduce the need for technically adept students across the economy, both public and private. I think we can present a strong case to non-CS majors: “You can be a good X, but add CS and you will be among the best!”\n\nI think this program can obviously appeal to engineering, science and technical majors such as economics, etc. However, I also think it is an opportunity to expand into liberal arts and social sciences. I can imagine a “CS+” minor being extremely useful for a sociology major, for example, and hope we can reach out to students in those departments once we have established the shape of our program.\n\nThis may not just be a matter of my own preferences (I admit I like this idea conceptually, not just practically): if CS departments do not consider this sort of program they may see falling enrollment as students avoid the CS major due to AI fear.\n\n### Network Isolated Computers\n\nI think it would be very forward-thinking for computer science departments to create network isolated computer labs.\n\nThese systems could be used to assess student competency while still providing a nice computing environment with IDEs, etc. without the risk of AI-generated solutions.\n\nI can imagine assignments and quizzes that utilize this resource quite effectively. It would be a large investment and require management and upkeep, but could be very valuable if done well and perhaps become a centerpiece of a department’s teaching strategy.\n\n### Interview-Based Grading\n\nFinally, another way to avoid AI poisoning the evaluation of students is, again, returning to the past: sitting down and having [a conversation](https://en.wikipedia.org/wiki/Oral_exam) with a student to determine their grade.\n\nWhile I do not have experience directly with oral exams, my experience in office hours tells me that I can determine the competence of a given student in roughly five minutes of conversation, guiding the conversation to the level that they are comfortable with, giving hints where necessary, etc. and determine a reasonable grade for them in a manner that would be very hard to game.\n\nNow, many of my classes have 100+ students, and if it took, say, 15 minutes per student interview, that is 25 hours of total interview time. This is not realistic given the current structure of classes (I currently have 3 hours of lecture over 15 weeks, or 45 total hours.)\n\nHowever, a forward-looking university might restructure finals week (or perhaps two finals weeks) in such a way to allow oral exams, and it would provide a much better final analysis of students’ achievement in a class.\n\n(Of course it would be a lot more work for professors, so I view this as unfortunately unlikely.)\n\n# Conclusion\n\nSo, yes, I think The University is still relevant in the post-AI era and, in fact, may become *more* relevant due to some structural advantages it has, particularly in signaling student competence to the outside world.\n\nI do think computer science departments will need to adjust to the new realities and consider some somewhat radical changes in order to maximize their value to their students. Most importantly, I think universities should increase their focus on providing hand-coding opportunities to students, since these opportunities are becoming less available after students leave school.\n\nI hope that this essay helps computer science educators improve their course offering so that we can continue to produce competent and confident computer scientists for the foreseeable future.\n\n*Note: No AI was used in the writing of this essay. AI was used to correct typos and to produce the table of contents.*","publishedAt":"2026-06-11T00:00:00.000Z","fetchedAt":"2026-06-12T05:00:37.597Z","url":"https://htmx.org/essays/universities-and-ai/","media":[],"coverageCount":0},{"id":"rel_cNOUHo9nBjXmr-KXoLNxN","version":null,"type":"feature","title":"Code is Cheap(er)","summary":"There is no getting around the fact that, in the last year, code has gotten much cheaper to create. AI is able to generate reams and reams of code, of...","titleGenerated":null,"titleShort":null,"content":"There is no getting around the fact that, in the last year, code has gotten much cheaper to create. AI is able to generate reams and reams of code, often of reasonably decent quality, incredibly quickly. There is no point in pretending that this isn’t the case.\n\nAt times, when confronted with this admittedly uncomfortable fact, I have seen people I respect say something like “coding was never the problem.”\n\nWhile I appreciate the sentiment, I don’t completely agree with that: certainly coding was at least *part* of the problem.\n\nAnd that part of the problem has shrunk significantly with the advent of effective AI coding tools.\n\nSo what does raw coding becoming less important mean for software developers, people who, in the past, prided themselves (and often compared themselves) on their ability to code?\n\n## Understanding is Expensive(er)\n\nOne thing I see is that it means that *understanding* code has become more expensive. This is because when reams and reams of code are generated, rather than emerging painfully from a particular programmer’s fingers, there *is* no understanding of that code.\n\nIn as much as understanding that code needs to exist, it has to be done after the code is written, by reading the code.\n\nNote that conventional wisdom is that reading someone else’s code is harder than writing your own code.\n\nSome AI enthusiasts say “Who cares, you don’t understand the output of compilers.”\n\nI think that is a category error for multiple reasons:\n\n- Compilers are deterministic; LLMs are, by design, not\n- Compiler workflows retain their original source code; LLM workflows typically do not\n- Compiler output is to a narrowly constrained domain (machine code); LLM output is not (generalized software)\n\nI maintain that, in most cases and certainly for mission-critical software, developers still need to understand the underlying code even if it is generated by an LLM.\n\nAnd if code *is* generated by an LLM there is a stark danger: the LLM can produce code far faster than you, or anyone else, can understand it. This is why I recommend incremental use of LLMs rather than allowing them to generate massive changelists that neither you, nor anyone else, can understand.\n\n(There are times when this can be appropriate, such as in a mechanical refactor, but it is extremely dangerous when new semantics are being introduced into a code base.)\n\n## The Sorcerer’s Apprentice Trap\n\nOne movie scene that has been consistently coming back to me as I have watched AI garner more and more attention is [The Sorcerer’s Apprentice](https://video.disney.com/watch/sorcerer-s-apprentice-fantasia-4ea9ebc01a74ea59a5867853) from Disney’s movie *Fantasia*.\n\nIn this scene the apprentice decides to use magic to assist in the drudgery of cleaning. He enchants a broom which then proceeds to start cleaning things up. Things appear to be going swimmingly for a while, until the broom starts cleaning more and more vigorously, reaching a point where things start going swimmingly literally.\n\nThe chaos is resolved when the Sorcerer reappears and asserts control over the situation, glaring at the apprentice for his foolishness.\n\nThis seems like an apt metaphor for the AI era: you want to be a sorcerer and not an apprentice.\n\nAnd a sorcerer has to understand the code.\n\n## Complexity: Still Bad\n\nHumans, generally, have a poor grasp of geometric and exponential curves.\n\n(This is why they believe in fairy tales such as compound interest.)\n\nThe core danger of code being cheap is [complexity](https://grugbrain.dev), which I assert, without proof, tends to grow at least geometrically and often exponentially with the size of a system.\n\nBefore LLMs there were prolific human coders.\n\nPerhaps you have worked with one: they can write *a lot* of code.\n\nI have seen prolific coders who lack a proper fear of complexity heap more and more code on top of an existing problem until the whole system collapses into an unmodifiable steady state, where any change creates as many bugs as it fixes.\n\nLLMs are incapable of fear of complexity, and are prolific coders.\n\nSeems dangerous to me.\n\n## The Subtractive, Constraining Engineer\n\nTo address this danger of LLM-generated code, I propose the subtractive, constraining engineer:\n\nThis engineer [says no](https://grugbrain.dev/#grug-on-saying-no), closely examines LLM output, suggests simplifications and generally retains a firm hand when dealing with LLM-generated code.\n\nRather than priding themselves on the code they create, they pride themselves on the code (and layers) they *remove* from or prevent from entering systems.\n\nThis ethos is more sculptor and less builder.\n\nWhere the builder ethos still applies, to an extent, is at the system design level: a good engineer will need to know how to compose components effectively to create systems. However, even here, I think that the subtractive mindset will be useful: removing unnecessary components and system boundaries to simplify system deployment and inter-component interactions, etc.\n\nThe subtractive engineer is a different kind of engineer than most coders have been in the past. I will admit that I have always been sympathetic to the subtractive engineer mindset: I don’t mind saying no, I don’t mind polishing existing systems rather than heroic rewrites, etc.\n\nBut, admitting my own biases, I believe this approach is a productive way to engage with LLMs that retains the art of computer programming and properly acknowledges a dual reality: code has gotten much cheaper to create and complexity remains our apex predator.","publishedAt":"2026-06-04T00:00:00.000Z","fetchedAt":"2026-06-04T23:00:46.448Z","url":"https://htmx.org/essays/code-is-cheap/","media":[],"coverageCount":0},{"id":"rel_rVPL4U-bmFH9Vdaml7_CN","version":null,"type":"feature","title":"Hypermedia Friendly Model Context Protocol App Architecture","summary":"I am working on [speedystride.com](https://speedystride.com), a programming tool that helps athletes quickly input\nworkouts on their Apple and Garmin ...","titleGenerated":null,"titleShort":null,"content":"I am working on [speedystride.com](https://speedystride.com), a programming tool that helps athletes quickly input\nworkouts on their Apple and Garmin watches.\n\nThese watches come with a built-in workout programming feature that is especially useful for structured programs. For\nexample, runners will often do interval training, which could be something like 5x1000m with 2 minute rest.\n\nAnd sometimes they’ll want to do a fartlek (Swedish for ‘Speed Play’) where they will vary their speed: run 400 meters\nfast - run 800 meters slower - sprint 200 meters.\nThese smartwatches will vibrate and beep to help the user perform at the desired target, and also count down rest\nperiods so the user is rested\nenough for that next hard interval.\n\nUnfortunately, I did not like any of the first party workout builders. These are form-based, with a drag-and-drop\ninterface to structure your workouts.\nI think these builders have a high user friction; more user inputs are required in proportion to the output.\nAdditionally, these\nbuilders [run on a small watch screen](https://support.apple.com/en-gb/guide/watch/apd66fcd5c5c/watchos),\nor require a separate app.\nThis is less than ideal when you are trying to program your watch right before a track workout. There are third party\ntools in this space, but as far as I can tell, they do not fundamentally break this pattern.\n\nI also wanted to share these workouts with everyone at my city’s track club. My service provides a scheduler that pushes\nworkouts automatically at a specified time for our club training; about 90% of our members have either an Apple Watch\nor a Garmin so cross-platform compatibility is a very important factor.\n\nTo solve these problems, I came up with a very simple domain specific language (DSL) for both people and machines.\nIt can describe exercises, define rest times, and combine everything together into repeat intervals.\nI implemented a simple recursive descent parser, and it outputs data formats for both Apple and Garmin devices.\nBy defining a small language, I was able to avoid implementing complex forms unlike the current offerings. User input is\nreduced\nto plain text.\n\n### Example workout DSL\n\nUser:\n\n```\n10x200m max effort with 2 minute rest\n\n```\n\nDSL:\n\n```\nRepeat 10 times:\n- Run 200m @RPE 10\n- Rest 2 minutes\n\n```\n\nI had initially wanted coaches to learn this DSL and enter programs into my website assisted by a Codemirror editor.\nI incorrectly thought that it was close enough to English for people to quickly learn it when assisted by autocomplete\nfeatures.\nI was not meeting my users where they were at; graybeard track coaches had zero interest in learning how to program.\nWhat I needed\nwas a translator that could convert natural English into my DSL.\n\n## Model Context Protocol (MCP) and MCP Apps\n\nAs I started sharing my project with other people, large language models were becoming popular, and it was an obvious\ntool to translate natural English workouts into my training DSL.\nBy integrating LLMs, I can massively reduce user friction. There are no forms with complex UIs to implement. There is no\nDSL to learn as the AI can translate natural\nlanguage for you. Users can now express their workouts in their own way.\n\nAn AI can transform the above user input to this Domain Specific Language with a relatively small language\nspecification. There was also a nice side effect of being very token efficient.\nJSON payloads defining a repeated workout set can get quite large while my DSL can stay compact.\nAny errors can be corrected as my parser can provide rich feedback on what went wrong. I have found that 95% of interval\nworkouts I see can be expressed through my language.\n\nLLMs also enable new capabilities, such as programming your watch from a photo of a whiteboard.\nEven more importantly, Model Context Protocol (MCP) was starting to gain traction. MCPs are a way for LLM systems to\ninteract with the real world, which means that besides\njust outputting workout programs, the LLM can call a remote function to actually send that workout to your device.\n\nAnthropic and OpenAI both support MCP. So it would be awesome for my business to support LLM integrations since so many\nusers already have Claude and ChatGPT installed on their phone.\n\nStill, there were opportunities to further improve user experience.\n\nI had mentioned earlier that my track club uses speedystride.com to program members’ watches. In order to do so, we have\nto define a few parameters:\n\n- What is the workout?\n\n- Should the workout be added to my Monday night track intervals, or for Tuesday fartleks?\n\nLLMs can help massively with the first question. But how about the second? Much of the current human-to-LLM interaction\nis text-based, and MCPs are no exception.\nBesides improving workout building UX, AI tools introduced new frictions. To associate a workout with an event, I had an\nevents tool that would fetch upcoming events for the user and add them\nto the LLM context. Then it was up to the LLM to guide the user. Some systems like Claude do provide simple select\ncontrols if your tools output JSON objects that look like a set of choices.\nHowever, this interaction forces the developer to surrender control of the happy path, which often leaves users\nconfused. Also, the AI would sometimes try to be too helpful and just guess the tool inputs.\nIn summary, back and forth conversation with the LLM is not an ideal UX as the users have to figure out how to guide the\nAI to the right inputs.\n\nA form with a selector interface is an obvious way to solve this problem.\n\nLuckily for me, the new MCP Apps specification was released in January 2026. This is an extension to the MCP\nspecification\nthat allows rendering custom UI inside an `` of the MCP Host.\n\nReferences:\n\n- [MCP](https://modelcontextprotocol.io/docs/getting-started/intro)\n\n- [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview)\n\n### MCP App Architecture\n\nYou need to host an MCP server that can communicate with the AI systems.\n\n#### Communication model:\n\n```\nMCP Server  LLM Host (Claude or ChatGPT)  MCP App UI (rendered inside LLM )\n\n```\n\nAll traffic between the MCP App UI and the LLM host must be routed through the App Bridge. The LLM host will then make\nproxied requests to my MCP server.\n\n### Interactive Hypermedia UIs in MCP Apps\n\nLet’s say that we are developing a simple workout scheduler, where we have the LLM generated workout program. Our goal\nis to\nassociate this workout with a calendar event occurrence.\nA user could have multiple events on her calendar, so a dynamic choice of occurrences should be available for each event\nshe is subscribed to.\nOn a traditional website, this could be trivially handled by a full page refresh.\n\nOn MCP App systems without interactivity, we would have to ask the LLM to fully render the MCP App\nin the chat. This adds friction and unnecessarily consumes tokens. So we must find a path to interactivity within the\nsame UI context.\n\nThere are existing UI toolkits, such as [MCP-UI](https://mcpui.dev) that works really well with React. However, using\nReact for a simple `` felt too complex, so I wanted a hypermedia solution.\n\nI initially considered using HTMX, since my HTTP site uses it already. The challenge was that HTMX is heavily geared\ntowards processing HTTP requests.\nSince my MCP App UI renders inside an ``, I don’t need to push URLs or manage history.\n\nThen I remembered [Fixi](https://github.com/bigskysoftware/fixi) library from the creator of HTMX. It is a minimal\nversion of generalized hypermedia controls. Fixi proved to be very useful precisely because it doesn’t do much.\nIn addition to lacking history or URL push features, I can also copy & paste Fixi to my project to skip build steps. I\nalso don’t have to worry about CSP configuration if my Fixi code is served with my HTML.\n\nThe killer feature of Fixi is its [hackability](https://github.com/bigskysoftware/fixi?tab=readme-ov-file#mocking).\nBecause it expects a Response compatible object (or a Promise) rather than a strict network request, we can entirely\nbypass HTTP.\nI can configure Fixi to call `app.callServerTool` when it sees any `fx-action` that starts with `tool:`. If I use a\n``, its inputs will become the function args.\n\nLet’s examine how to architect MCP features to serve hypermedia.\n\n#### MCP App Lifecycle\n\nFrom [MCP docs](https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html#architecture):\n\n- Create: Instantiate App with info and capabilities\n\n- Connect: Call `connect()` to establish transport and perform handshake\n\n- Interactive: Send requests, receive notifications, call tools\n\n- Cleanup: Host sends teardown request before unmounting\n\nWe will focus on items 2 and 3 to demonstrate a hypermedia driven MCP App.\n\n#### The MCP Server\n\n##### MCP Server Architecture\n\nI host my server on a Gunicorn + Uvicorn monolith. ASGI Django handles regular HTTP traffic, and MCP traffic is routed\nto\nFastMCP. Since both Django and FastMCP work together, I can share resources between the HTTP and MCP domains including\nORM and template rendering.\n\nThe LLM host will render a UI by calling an MCP **tool** and its associated **resource**.\n\nA **tool** is similar to a view- it is a function that can return JSON or hypertext. Let’s say that our UI tool is\ncalled `show_user_ui`.\n\nA **resource** is a bit like a pointer to assets. LLM hosts can preload resources to deliver them more quickly to users.\n\nTools and resources are registered by `@mcp.tool` or `@mcp.resource` decorators. You could think of registration as\ndefining `urls.py` in Django.\n\nSince our Django and FastMCP Applications live on the same server, we can render HTML with Django’s `render_to_string`\nfunction with ORMs and templatetags.\nDjango 6’s\nnew [built-in template partials](https://docs.djangoproject.com/en/6.0/ref/templates/language/#template-partials) also\nmake\ntemplate organization and partial rendering easy. This allows us to co-locate our initial UI render and our dynamic\nhypermedia fragments in a single file, keeping the MCP tool logic incredibly clean.\n\n##### After lifecycle item 2: The initial MCP App render\n\nLet’s talk about the **resource** first. It points to a HTML where our Fixi and MCP App Bridge code will be placed.\n\n```\nfrom django.template.loader import render_to_string\n\n@mcp.resource(\n    \"ui://user_ui_resource.html\",\n    mime_type=\"text/html;profile=mcp-app\",\n    description=\"User UI resource\",\n)\nasync def user_ui_resource():\n    return render_to_string(\"mcp/user_ui_resource.html\")\n\n```\n\nThis resource serves HTML rendered with `render_to_string` to resolve static files and template tags, but not any\nuser-specific data.\n\nmcp/user_ui_resource.html\n\n```\n\nhtml>\nhead>\n    {% include \"fixi, app bridge source code, styles, etc\" %}\n\n    style>\n        /* CSS indicator classes to toggle visibility during requests */\n        #indicator {\n            display: none;\n        }\n\n        #indicator.fixi-request-in-flight {\n            display: inline-block;\n        }\n    style>\n\n    script type=\"module\">\n        ...\n        MCP\n        App\n        bridge\n        setup\n        ...\n\n        // 1. Configure Fixi to route `tool:` fx-actions through MCP App bridge\n        document.addEventListener(\"fx:config\", (evt) => {\n            const action = evt.detail.cfg.action;\n            if (action.startsWith(\"tool:\")) {\n                const toolName = action.replace(\"tool:\", \"\");\n                console.log(`callServerTool: ${toolName}`);\n                const args = Object.fromEntries(evt.detail.cfg.body ?? []);\n                evt.detail.cfg.fetch = async () => {\n                    const result = await app.callServerTool({name: toolName, arguments: args});\n                    return {text: async () => result.structuredContent?.html};\n                };\n            }\n        });\n\n        // 2. Set up request indicator extension: ext-fx-indicator\n        document.addEventListener(\"fx:init\", (evt) => {\n            if (evt.target.matches(\"[ext-fx-indicator]\")) {\n                let disableSelector = evt.target.getAttribute(\"ext-fx-indicator\")\n                evt.target.addEventListener(\"fx:before\", () => {\n                    let disableTarget = disableSelector === \"\" ? evt.target : document.querySelector(disableSelector)\n                    disableTarget.classList.add(\"fixi-request-in-flight\")\n                    evt.target.addEventListener(\"fx:after\", (afterEvt) => {\n                        if (afterEvt.target === evt.target) {\n                            disableTarget.classList.remove(\"fixi-request-in-flight\")\n                        }\n                    })\n                })\n            }\n        });\n\n        // 3. Populate UI on initial render with dynamic content fetched with `show_user_ui` tool\n        app.ontoolresult = (params) => {\n            document.body.innerHTML = params.structuredContent?.html ?? document.body.innerHTML;\n        };\n    script>\nhead>\n\n{# Empty body, since `app.ontoolresult` will populate it on load #}\nbody>body>\n\n{% partialdef save-form-fragment %}\ndiv id=\"save-form-fragment\">\n    Workout {{ workout.id }} has been saved for {{ workout.occurrence.event.id }} on {{ workout.occurrence.start_date\n    }}.\ndiv>\n{% endpartialdef %}\n\n{% partialdef main-contents %}\ndiv id=\"main-contents\">\n    form>\n        input name=\"workout_dsl\" type=\"hidden\" value=\"{{workout_dsl}}\">\n\n        select\n                id=\"event-selector\"\n                name=\"event_id\"\n                fx-action=\"tool:render_occurrences_fragment\"\n                fx-target=\"#occurrence-fragment\"\n                fx-swap=\"outerHTML\"\n        >\n            option>-----option>\n            {% for evt in events %}\n            option value=\"{{ evt.id }}\">{{ evt.name }}option>\n            {% endfor %}\n        select>\n\n        {% partialdef occurrence-fragment inline %}\n        select id=\"occurrence-fragment\" name=\"occurrence_id\">\n            {% for occ in occurrences %}\n            option value=\"{{ occ.id }}\">{{ occ.start_date }}option>\n            {% endfor %}\n        select>\n        {% endpartialdef %}\n\n        button\n                type=\"submit\"\n                fx-action=\"tool:save_form_fragment\"\n                fx-swap=\"outerHTML\"\n                fx-target=\"#main-contents\"\n                ext-fx-indicator\n        >\n            span>Save Workoutspan>\n            svg id=\"indicator\">...svg>\n        button>\n    form>\ndiv>\n{% endpartialdef %}\nhtml>\n\n```\n\nThis HTML resource defines three core pieces of logic within its `` tag:\n\n- Event listener for `fx:config`: We can use `fx-action` attribute to make MCP tool calls.\n\n- Event listener for `fx:init`: Set up an indicator to show that a tool call is being processed.\n\n- Handle `app.ontoolresult`: Display the main UI with the output by processing `CallToolResult`.\n\nThe LLM will load the **resource** in the chat, and then call `show_user_ui` **tool**, which renders the `main-contents`\ntemplate partial.\n\n```\nfrom django.template.loader import render_to_string\n\nfrom mcp.server.fastmcp import FastMCP\nfrom mcp.server.fastmcp import Context\nfrom mcp.types import CallToolResult\n\nfrom events.models import Events, Occurrences\nfrom workout_validator import validate_program, DSLValidationError\n\nmcp = FastMCP(...)\n\n@mcp.tool(\n    name=\"show_user_ui\",\n    description=\"Display UI\",\n    meta={\"ui/resourceUri\": \"ui://user_ui_resource.html\"}\n)\nasync def show_user_ui(ctx: Context, workout_dsl: str) -> CallToolResult:\n    user = await get_user_from_context(ctx)\n\n    try:\n        validate_workout = validate_program(workout_dsl)\n    except DSLValidationError as e:\n        ...\n        handle\n        invalid\n        workout\n        data...\n\n    initial_events_qs = Events.objects.filter(user=user)\n    initial_events = [evt async for evt in initial_events_qs.aiterator()]\n\n    if len(initial_events) > 0:\n        initial_occurrences_qs = Occurrences.objects.select_related(\"event\").filter(\n            event_id=initial_events[0]).order_by(\"start_date\")\n        initial_occurrences = [occ async for occ in initial_occurrences_qs.aiterator()]\n    else:\n        initial_occurrences = []\n\n    template_context = {\"user\": user, \"workout_dsl\": workout_dsl, \"events\": initial_events,\n                        \"occurrences\": initial_occurrences}\n    rendered_html = render_to_string(\"mcp/user_ui_resource.html#main-contents\", template_context)\n\n    return CallToolResult(\n        content=[TextContent(type=\"text\", text=\"UI ready\")],\n        structuredContent={\"html\": rendered_html}\n    )\n\n```\n\nArgs for `show_user_ui`:\n\n- `ctx`: object will allow us to authenticate our users, and is passed to our tool by the LLM host.\n\n- `workout_dsl`: AI will generate and pass this parameter. We will render our HTML with this data stored in\n`` and save it later.\n\nReturn values from `show_user_ui`:\n\n- content: An array of text or other data to show the User/LLM\n\n- structuredContent: Rendered hypertext\n\nMCP’s `ontoolresult` handler will insert `structuredContent.html` into `` tag to render our initial UI.\n\nNote that this `innerHTML` assignment is reasonably safe in our context. The only untrusted input here is AI generated\n`workout_dsl`, and we run validation on it before rendering our template.\n\n#### Lifecycle 3: Rendering Hypermedia Fragments\n\nNow we have a full `` with two `` controls. How do we add interactivity for our event occurrence\nselectors?\nEvery time the user changes `#event-selector` option, we should update our options shown in `#occurrence-fragment` with\na new tool.\nWe need to use template fragments to fetch occurrence options for different events without triggering another UI render.\n\nLet’s first examine how Fixi will trigger a fragment request. Scroll back above and read `#event-selector` defined in\n`#main-contents`.\n\nWe see these Fixi attributes:\n\n- fx-action: Calls `render_occurrences_fragment` MCP tool. Fixi will bind `fx-action` to appropriate default events,\nsuch as `change` for ``.\n\n- fx-target: Insert `render_occurrences_fragment` tool’s HTML output in the `#occurrence-fragment` div\n\n- fx-swap: Use `outerHTML` swap on `#occurrence-fragment`, which replaces this div instead of inserting contents.\n\nBelow shows occurrence HTML fragment rendering `occurrence-fragment` Django template partial. This tool’s output will\nreplace `#occurrence-fragment`.\n\n```\nfrom events.models import Occurrences\n\n@mcp.tool(\n    name=\"render_occurrences_fragment\",\n    description=\"Fetches event occurrences available to the logged in user\",\n    meta={\"ui\": {\"visibility\": [\"app\"]}}\n)\nasync def render_occurrences_fragment(ctx: Context, event_id) -> CallToolResult:\n    ...\n    validate\n    event_id and authenticate\n    user...\n\n    occurrences_qs = Occurrences.objects.select_related(\"event\").filter(\n        event_id=event_id\n    ).order_by(\"start_date\")\n    occurrences = [occ async for occ in occurrences_qs.aiterator()]\n\n    rendered_html = render_to_string(\"mcp/user_ui_resource.html#occurrence-fragment\", {\"occurrences\": occurrences})\n    return CallToolResult(\n        text=\"Here are this user's event occurrences\",\n        structuredContent={\"html\": rendered_html}\n    )\n\n```\n\nIt does not make sense for LLM to fetch this HTML fragment by itself, so we can set\n`meta={\"ui\": {\"visibility\": [\"app\"]}}` registration parameter to prevent unnecessary tool calling.\n\nFinally, let’s submit this form. This action is triggered by ``, and all of the `` fields will be sent to\n`save_form_fragment` as function args.\n\n```\nfrom workouts.models import Workout\nfrom events.models import Events, Occurrences\n\n@mcp.tool(\n    name=\"save_form_fragment\",\n    description=\"Saves workout\",\n    meta={\"ui\": {\"visibility\": [\"app\"]}}\n)\nasync def save_form_fragment(ctx: Context, workout_dsl, event_id, occurrence_id) -> CallToolResult:\n    ...\n    validate\n    args and authenticate\n    user...\n    occurrence = await Occurrences.objects.select_related(\"event\").aget(id=occurrence_id, event_id=event_id)\n    workout = await Workout.objects.acreate(occurrence=occurrence, workout_dsl=workout_dsl)\n\n    # Render the events fragment\n    rendered_html = render_to_string(\"mcp/user_ui_resource.html#save-form-fragment\", {\"workout\": workout})\n    return CallToolResult(\n        text=\"Workout saved\",\n        structuredContent={\"html\": rendered_html}\n    )\n\n```\n\nThe submit indicator is defined by configuring Fixi event `fx:init` to add `.fixi-request-in-flight` class to\n`#indicator` SVG inside our submit button if it has `ext-fx-indicator` attribute.\nAfter this, the interaction cycle is finished. If the user wants to make modifications, she can ask the AI to render a\nnew UI or visit the website to make quick changes.\n\n## Demo\n\nHere is a demo of this application in action:\n\n![](/img/mcp_hypermedia_example.gif)\n\n## Conclusion\n\nI hope to have demonstrated a simple way to develop user interfaces that work well with AI.\nMCP Apps introduce a new rendering environment, but hypermedia systems continue to work well in this context with some\nmodifications.\n\nThis is worth emphasizing because the current zeitgeist when building with AI is to reach for a client-side framework.\nBut the constraints of MCP Apps actually push in the opposite direction. Your `` cannot talk directly to your\nserver and every interaction must cross the bridge.\nThe less state and logic you pack into the client, the less surface area you have for things to go wrong across that\nboundary.\n\nThroughout my design process, I tried to channel Nintendo’s Gunpei Yokoi: What can we do with old-fashioned technology\nusing lateral thinking?\nI was able to sidestep complex drag-and-drop form builders by combining a small DSL with AI and hypermedia.\nIntegrating MCP Apps solved the friction of coaxing the AI to select the right inputs, and using hypermedia made the\nentire system almost trivially simple: forms, selectors, fragment updates, loading indicators.\nSimplicity does not guarantee success, but I think that it will give me a better fighting chance.\n\nSpecial thanks to Carson Gross for creating Fixi, HTMX, and Hyperscript, and for encouraging me to write this post.","publishedAt":"2026-03-18T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.807Z","url":"https://htmx.org/essays/mcp-apps-hypermedia/","media":[],"coverageCount":0},{"id":"rel_nfEVJXCLg7Rbb52aPcIgE","version":null,"type":"feature","title":"Yes, and...","summary":"I teach computer science at [Montana State University](https://www.cs.montana.edu).  I am the father of three sons who\nall know I am a computer progra...","titleGenerated":null,"titleShort":null,"content":"I teach computer science at [Montana State University](https://www.cs.montana.edu).  I am the father of three sons who\nall know I am a computer programmer and one of whom, at least, has expressed interest in the field.  I love computer\nprogramming and try to communicate that love to my sons, the students in my classes and anyone else who will listen.\n\nA question I am increasingly getting from relatives, friends and students is:\n\nGiven AI, should I still consider becoming a computer programmer?\n\nMy response to this is: “Yes, and…”\n\n## “Yes”\n\nComputer programming is, fundamentally, about two things:\n\n- Problem-solving using computers\n\n- Learning to control complexity while solving these problems\n\nI have a hard time imagining a future where knowing how to solve problems with computers and how to control the complexity\nof those solutions is *less* valuable than it is today, so I think it will continue to be a viable career even with the\nadvent of AI tools.\n\n### “You have to write the code”\n\nThat being said, I view AI as very dangerous for junior programmers because it *is* able to effectively generate code for\nmany problems.  If a junior programmer does not learn to write code and simply generates it, they are robbing\nthemselves of the opportunity to develop the visceral understanding of code that comes with being down in the trenches.\n\nBecause of this, I warn my students:\n\n“Yes, AI can generate the code for this assignment. Don’t let it. You *have* to write the code.”\n\nI explain that, if they don’t write the code, they will not be able to effectively *read* the code.  The ability to\nread code is certainly going to be valuable, maybe *more* valuable, in an AI-based coding future.\n\nIf you can’t read the code you are going to fall into [The Sorcerer’s Apprentice Trap](https://www.youtube.com/watch?v=m-W8vUXRfxU),\ncreating systems [you don’t understand and can’t control](https://www.youtube.com/watch?v=GFiWEjCedzY).\n\n### Is Coding → Prompting like Assembly → High Level Coding?\n\nSome people say that the move from high level languages to AI-generated code is like the move from assembly to\n[high level programming languages](https://en.wikipedia.org/wiki/High-level_programming_language).\n\nI do not agree with this simile.\n\nCompilers are, for the most part, deterministic in a way that current AI tools are not.  Given a high-level programming\nlanguage construct such as a for loop or if statement, you can, with reasonable certainty, say what the generated\nassembly will look like for a given computer architecture (at least pre-optimization).\n\nThe same cannot be said for an LLM-based solution to a particular prompt.\n\nHigh level programming languages are a *very good* way to create highly specified solutions to problems\nusing computers with a minimum of text in a way that assembly was not.  They eliminated a lot of\n[accidental complexity](https://en.wikipedia.org/wiki/No_Silver_Bullet), leaving (assuming the code was written\nreasonably well) mostly necessary complexity.\n\nLLM generated code, on the other hand, often does not eliminate accidental complexity and, in fact, can add\nsignificant accidental complexity by choosing inappropriate approaches to problems, taking shortcuts, etc.\n\nIf you can’t read the code, how can you tell?\n\nAnd if you want to read the code you must write the code.\n\n### AI is a great TA\n\nAnother thing that I tell my students is that AI, used properly, is a tremendously effective TA.  If you don’t use it\nas a code-generator but rather as a partner to help you understand concepts and techniques, it can provide a huge boost\nto your intellectual development.\n\nOne of the most difficult things when learning computer programming is getting “stuck”.  You just don’t see the trick\nor know where to even start well enough to make progress.\n\nEven worse is when you get stuck due to accidental complexity: you don’t know how to work with a particular tool chain\nor even what a tool chain is.\n\nThis isn’t a problem with *you*, this is a problem with your environment.  Getting stuck pointlessly robs you of time to\nactually be learning and often knocks people out of computer science.\n\n(I got stuck trying to learn Unix on my own at Berkeley, which is one reason I dropped out of the computer science\nprogram there.)\n\nAI can help you get past these roadblocks, and can be a great TA if used correctly.  I have posted an\n[AGENTS.md](https://gist.github.com/1cg/a6c6f2276a1fe5ee172282580a44a7ac) file that I provide to my students to configure\ncoding agents to behave like a great TA, rather than a code generator, and I encourage them to use AI in this role.\n\nAI doesn’t *have* to be a detriment to your ability to grow as a computer programmer, so long as it is used\nappropriately.\n\n## “, and…”\n\nI do think AI is going to change computer programming.  Not as dramatically as some people think, but in some\nfundamental ways.\n\n### Raw coding may become less important\n\nIt may be that the *act* of coding will lose *relative* value.\n\nI regard this as too bad: I usually like the act of coding, it is fun to make something do something with your\n(metaphorical) bare hands.  There is an art and satisfaction to writing code well, and lots of aesthetic decisions to be\nmade doing it.\n\nHowever, it does appear that raw code writing prowess may be less important in the future.\n\nAs this becomes relatively less important, it seems to me that other skills will become more important.\n\n### Communication Skills\n\nFor example, the ability to write, think and communicate clearly, both with LLMs and humans seems likely to be much more\nimportant in the future.  Many computer programmers have a literary bent anyway, and this is a skill that will likely\nincrease in value over time and is worth working on.\n\nReading books and writing essays/blog posts seem like activities likely to help in this regard.\n\n### Understanding Business\n\nAnother thing you can work on is turning some of your mental energy towards understanding a business (or government\nrole, etc) better.\n\nComputer programming is about solving problems with computers and businesses have plenty of both of these.\n\nSome business folks look at AI and say “Great, we don’t need programmers!”, but it seems just as plausible to me that\na programmer might say “Great, we don’t need business people!”\n\nI think both of these views are short-sighted, but I do think that AI can give programmers the ability to continue\nfundamentally working as a programmer while *also* investing more time in understanding the real-world problems (business or\notherwise) that they are solving.\n\nThis dovetails well with improving communication skills.\n\n### “Architecting” Systems\n\nLike many computer programmers, I am ambivalent towards the term “software architect.”  I have seen\n[architect astronauts](https://www.joelonsoftware.com/2001/04/21/dont-let-architecture-astronauts-scare-you/) inflict\na lot of pain on the world.\n\nFor lack of a better term, however, I think software architecture will become a more important skill over time: the\nability to organize large software systems effectively and, crucially, to control the complexity of those systems.\n\nA tough part of this for juniors is that traditionally the ability to architect larger solutions well has come from\nexperience building smaller parts of systems, first poorly then, over time, more effectively.\n\nMost bad architects I have met were either bad coders or simply didn’t have much coding experience at all.\n\nIf you let AI take over as a code generator for the “simple” stuff, how are you going to develop the intuitions necessary\nto be an effective architect?\n\nThis is why, again, you must write the code.\n\n### Using LLMs Effectively\n\nAnother skill that seems likely to increase in value (obviously) is knowing how to use LLMs effectively.  I think that\ncurrently we are still in the process of figuring out what that means.\n\nI also think that what this means varies by experience level.\n\n#### Seniors\n\nSenior programmers who already have a lot of experience from the pre-AI era are in a good spot to use LLMs effectively:\nthey know what “good” code looks like, they have experience with building larger systems and know what matters and\nwhat doesn’t.  The danger with senior programmers is that they stop programming entirely and start suffering from\n[brain rot](https://www.media.mit.edu/publications/your-brain-on-chatgpt/).\n\nParticularly dangerous is firing off prompts and then getting sucked into\n[The Eternal Scroll](https://theneverendingstory.fandom.com/wiki/The_Nothing) while waiting.\n\nAsk me how I know.\n\nI typically try to use LLMs in the following way:\n\n- To analyze existing code to better understand it and find issues and inconsistencies in it\n\n- To help organize my thoughts for larger projects I want to take on\n\n- To generate relatively small bits of code for systems I am working on\n\n- To generate code that I don’t enjoy writing (e.g. regular expressions & CSS)\n\n- To generate demos/exploratory code that I am willing to throw away or don’t intend to maintain deeply\n\n- To suggest tests for a particular feature I am working on\n\nI try not to use LLMs to generate full solutions that I am going to need to support.  I will sometimes use LLMs alongside\nmy manual coding as I build out a solution to help me understand APIs and my options while coding.\n\nI never let LLMs design the APIs to the systems I am building.\n\n#### Juniors\n\nJuniors are in a tougher spot.  I will say it again: you must write the code.\n\nThe temptation to vibe your way through problems is very, very high, but you will need to fight against that temptation.\n\nPeers *will* be vibing their way through things and that will be annoying: you will need to work harder than they do,\nand you may be criticized for being slow.  The work dynamics here are important to understand: if your company\nprioritizes speed over understanding (as many are currently) you need to accept that and not get fired.\n\nHowever, I think that this is a temporary situation and that soon companies are going to realize that vibe coding at\nspeed suffers from worse complexity explosion issues than well understood, deliberate coding does.\n\nAt that point I expect slower, more deliberate coding with AI assistance will be understood as the best way to utilize\nthis new technology.\n\nWhere AI *can* help juniors is in accelerating the road to senior developer by eliminating accidental complexity that often\ntrips juniors up.  As I said above, viewing AI as a useful although sometimes overly-eager helper rather than a servant\ncan be very effective in understanding the shape of code bases, what the APIs and techniques available for a particular\nproblem are, how a given build system or programming language works, etc.\n\nBut you must write the code.\n\nAnd companies: you must let juniors write the code.\n\n## Getting a Job Today\n\nThe questions I get around AI and programming fundamentally revolve around getting a decent job.\n\nIt is no secret that the programmer job market is bad right now, and I am seeing good CS students struggle to find\npositions programming.\n\nWhile I do not have a crystal ball, I believe this is a temporary rather than permanent situation.  The computer\nprogrammer job market tends to be cyclical with booms and busts, and I believe we will recover from the current bust\nat some point.\n\nThat’s cold comfort to someone looking for a job now, however, so I want to offer the specific job-seeking advice that\nI give to my students.\n\n### Family, Friends, Family of Friends\n\nI view the online job sites as mostly pointless, especially for juniors.  They are a lottery and the chances of finding\na good job through them are low.  Since they are free they are probably still worth using, but they are not worth\ninvesting a lot of time in.\n\nA better approach is the four F’s: Family, Friends & Family of Friends.  Use your personal connections to find positions\nat companies in which you have a competitive advantage of knowing people in the company.  Family is the strongest\npossibility.  Friends are often good too.  Family of friends is weaker, but also worth asking about.  If you know or\nare only a few degrees separated from someone at a company you have a much stronger chance of getting a job at that\ncompany.\n\nI stress to many students that this doesn’t mean your family has to work for Google or some other big tech company.\n\n*All* companies of any significant size have problems that need to be solved using computers.  Almost every company over 100\npeople has some sort of development group, even if they don’t call it that.\n\nAs an example, I had a student who was struggling to find a job.  I asked what their parent did, and they said they worked\nfor Costco corporate.\n\nI told them that they were in fact extremely lucky and that this was their ticket into a great company.\n\nMaybe they don’t start as a “computer programmer” there, maybe they start as an analyst or some other role.  But the\nability to program on top of that role will be very valuable and likely set up a great career.\n\n## Conclusion\n\nSo I still think pursuing computer programming as a career is a good idea.  The current job market is bad, no doubt, but\nI think this is temporary.\n\nI do think how computer programming is done is changing, and programmers should look at building up skills beyond\n“pure” code-writing.  This has always been a good idea.\n\nI don’t think programming is changing as dramatically as some people claim and I think the fundamentals of programming,\nparticularly writing good code and controlling complexity, will be perennially important.\n\nI hope this essay is useful in answering that question, especially for junior programmers, and helps people feel\nmore confident entering a career that I have found very rewarding and expect to continue to do for a long time.\n\nAnd companies: let the juniors write at least some of the code.  It is in your interest.","publishedAt":"2026-02-27T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.807Z","url":"https://htmx.org/essays/yes-and/","media":[{"type":"video","url":"https://i.ytimg.com/vi/m-W8vUXRfxU/hqdefault.jpg","alt":"Fantasia - The Sorcerer's Apprentice (Part 2)","linkUrl":"https://www.youtube.com/watch?v=m-W8vUXRfxU","r2Key":"releases/7d7fbd6d58331db12a44ee855ff363e43cec54091725c6846ed60bf0502a49a7.jpg","r2Url":"https://media.releases.sh/releases/7d7fbd6d58331db12a44ee855ff363e43cec54091725c6846ed60bf0502a49a7.jpg"},{"type":"video","url":"https://i.ytimg.com/vi/GFiWEjCedzY/hqdefault.jpg","alt":"Fantasia - The Sorcerer's Apprentice (Part 3)","linkUrl":"https://www.youtube.com/watch?v=GFiWEjCedzY","r2Key":"releases/d8b2e82d52b8adf04cebdfce948260eed739bffff19011766d44f00f30486612.jpg","r2Url":"https://media.releases.sh/releases/d8b2e82d52b8adf04cebdfce948260eed739bffff19011766d44f00f30486612.jpg"}],"coverageCount":0},{"id":"rel_x65zGOIQaYAvJ0Cip3vXc","version":null,"type":"feature","title":"Building Critical Infrastructure with htmx: Network Automation for the Paris 2024 Olympics","summary":"## A Bit of Background\n\nDuring my 6 years at Cisco, I developed numerous web applications to assist network engineers with highly complex\noperations, ...","titleGenerated":null,"titleShort":null,"content":"## A Bit of Background\n\nDuring my 6 years at Cisco, I developed numerous web applications to assist network engineers with highly complex\noperations, both in terms of the volume of tasks to accomplish and the rigor of procedures to follow. Networking is a\nspecialized field in its own right, where the slightest error can have disastrous consequences: a network failure, even\npartial, can deprive millions of people of essential services like the ability to make a simple phone call.\n\nThis criticality imposes strict requirements on code meant for network operations: it must be reliable, readable, and\nfree of unnecessary frills. If there’s a problem, you need to be able to immediately trace the data flow and fix it on\nthe spot. That’s why, for years, I’ve used very few design patterns and banned function calls that call functions that\ncall functions, and so on. Beyond 2 levels of calls, I abstain.\n\nFollowing this logic, I favor mature tools over the latest trends. Thus, the Django / Celery / SQLite stack had been in\nmy toolbox for a long time. But like everyone else in the 2010s, I built SPAs and had never heard of intercooler.js or\nhypermedia, and I understood REST the way it’s commonly described pretty much everywhere.\n\nFor the JS framework, I made a conservative choice (and a marginal one, I know): I chose Ember.js. My motivations were\nits strong backward compatibility during updates and native MVC support. This JS framework is excellent, and that’s\nstill my opinion.\n\nAfter watching David Guillot’s presentation on HTMX at DjangoCon Europe 2022, I dug into the subject and prototyped a\ncomponent that addressed one of my recurring needs. It’s a kind of datatable on which you can trigger actions. There’s a\ndemo video on the HTMX Discord [here](https://discord.com/channels/725789699527933952/909436816388669530/1042451443656966234).\n\nI was a beginner with HTMX and built it in 2-3 days (no AI :-) ). But what was interesting wasn’t so much having this\ncomponent quickly at hand : it was the 100% Django codebase. One codebase instead of two, one app to maintain, and no\nmore API contracts to manage between front and back.\n\nAnd once again, even though I was comfortable with the Ember.js framework, having a single project to maintain changes\neverything.\n\nA few weeks later, a concrete use case came up for a major French ISP: configuring L2VPNs on brand-new routers, in bulk,\nwithout configuration errors (obviously), based on configurations from old routers that were end-of-life and about to be\ndecommissioned. It was highly critical: a single router can handle thousands of clients, and… there are a lot of\nrouters.\n\nFrom that point on, I used the Django / Celery + HTMX + SQLite (and Hyperscript) stack and delivered the app in 5 weeks.\nMy goal was to guide the network engineer by the hand and spare them 100% of the repetitive, tedious work: they just had\nto click and confirm, everything was handled. Their role was now limited to their expertise, and if there was a problem,\nit was up to them to fix the network.\n\nThe project, initially estimated at 18 months, ultimately took 9. And we were lucky: there were no complex corner cases\nto handle. And even if there had been, we had plenty of time to deal with them.\n\nHTMX in all this?\nIf I had to develop the app as a SPA, it would have taken me at least twice as long. Why?\nAs a solo full-stack developer, simply switching back and forth between codebases is already time-consuming. And that’s\njust the tip of the iceberg: the front/back approach itself adds a layer of complexity that ends up weighing heavily on\nproductivity.\n\n## HTMX at the Olympic Games\n\nThe Paris 2024 Olympic Games network consisted of thousands of Cisco switches pre-configured to accept Wi-Fi access\npoints, which self-configured through an automation system developed by Orange and Cisco. Wi-Fi was the most common\nconnectivity mode at the Games. But in some cases, a physical connection was necessary, most often to plug in a video\ncamera, but not only. Sometimes there was simply no other choice but to rely on a cable to connect, and therefore to\nconfigure the relevant switch port. That’s where an application became necessary.\n\nWhen Pollux contacted me about his need, he already had a data model for his network services in a Django project.\nAdditionally, he could deploy services via CLI: part of the business logic was already in place. The problem was that\nservice deployment parameters needed to be consolidated in an application. In CLI, you have to manage different data\nsources, which can quickly become complicated for the user. So it was necessary to centralize these business parameters\nin a webapp, expose all the data needed to deploy a service, and provide a GUI to configure them.\n\nThe Games were approaching and Pollux didn’t have time to build the webapp: as the architect of the Olympic network, he\nwas overwhelmed by a colossal number of tasks. I showed him the L2VPN app mentioned above and specified the 5-week\ndelivery timeline. I told him that if it suited him, I could build him an HTMX webapp based on his existing Django\nproject and a Bootstrap CSS customized internally by Orange.\n\nWe agreed on an 8-week timeline to cover the need, which involved 3 connectivity services: Direct Internet Access,\nPrivate VLAN, and Shared Internet Access.\n\n## Web Dev with HTMX\n\nHTMX is somewhat a return to the roots of web development, and regardless of the web framework: Django, ROR, Symfony…\nYou rediscover everything that makes a web framework useful rather than turning it into a mere JSON provider. Sending\nHTML directly to the browser, storing the app state directly in the HTML. That’s what true REST is, and it’s so much\nsimpler to manage.\n\nIf you ask me what’s most striking, it’s certainly returning to very simple things like this:\n\n![progress bar](/img/paris-olympics-progress-bar.png)\n\n Progress bar from RCP Portal \n\nHow does this progress bar work?\n\nExactly like [the example in the docs](https://htmx.org/examples/progress-bar/)!\n\nWhy this choice? Because it’s coded in 10 seconds, because the app won’t have thousands of users on this internal tool,\nno scaling concerns: you can do good old data polling without any problem.\n\nAnd the end user? If I use old-school polling, they don’t care: what they want is the information. No SSE or WebSocket\nfor this use case, I don’t need it. And if the need ever arises, the WebSocket (or SSE) plugin is easy to set up.\n\nOne of the big advantages of the philosophy surrounding HTMX is the notion of [Locality of Behaviour](https://htmx.org/essays/locality-of-behaviour/). Let’s take this\nprogress bar: if you want to know how it works, just look at the page source. No need to go into documentation or the\ncodebase, just a right-click and “View Page Source”:\n\n```\n\ndiv\n        hx-get=\"/job/progress\"\n        hx-trigger=\"every 600ms\"\n        hx-target=\"this\"\n        hx-swap=\"innerHTML\">\n    div class=\"progress\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"0\"\n         aria-labelledby=\"pblabel\">\n        div id=\"pb\" class=\"progress-bar\" style=\"width:0%\">\n        div>\n    div>\n\n```\n\nYou immediately know that every 600ms, this part of the page is updated with the content returned by the view handling\nthe `/job/progress` endpoint. No mystery for the team taking over development who wants to modify something: everything\nyou need to know is right in front of you.\n\nAnd that’s exactly what HTMX is about: every component, every interaction remains visible, understandable, and\nself-documented directly in the HTML. This is important for what comes next.\n\n## HTMX is “AI friendly”™\n\nIn the early stages of app development, I focused on the most complex network service: DIA (Direct Internet Access). DIA\nfor the Olympic Games meant many business parameters with many rules to apply.\n\nThe DIA creation form calls an endpoint that triggers a very long function, close to 600 lines of code.\n\nWhy such a long function?\n\nBecause it’s more readable and efficient to concentrate the data flow in one place, rather than dispersing it across a\nmultitude of layers and patterns.\n\nAn application is a wrapper around data: it orchestrates the data flow (data must flow!) and CRUD operations. But what\ncontrol do you retain when this flow is obfuscated in complex patterns or dispersed across 2 codebases?\n\nThe data flow must remain readable for the developer.\n\nHTMX, by allowing you to manage the GUI directly server-side, makes this flow even clearer. The same endpoint can return\nHTML fragments to signal that certain form data is invalid, or conversely indicate that a service deployment has\nstarted. You can thus act on any part of the GUI within the same function, while transforming the data to pass it to the\nsystem that configures the network switch.\n\nIn a traditional frontend/backend approach, this would be more complex: two applications to manage, and a much less\nreadable data flow.\n\nThis drastic code simplification enabled by HTMX, combined with a procedural approach, produces compact and transparent\nlogic, easy to navigate for a developer… or for an LLM, as I discovered.\n\nFor the Private VLAN (PVLAN) network service, the “shape” of the main function is roughly the same as for DIA: input\nparameters, validation, then interactions with the GUI via HTML fragments, and, if everything is correct, switch\nconfiguration.\n\nThe difference? PVLAN is simpler to handle: fewer form parameters and a bit less business logic.\n\nSo I took the long DIA function and gave it to an LLM (Claude 3 had just been released), with a prompt specifying the\nparameters specific to PVLAN. In seconds, Claude returned a new adapted function, and the same for the HTML templates.\nResult: about 80% of the code was ready, with only a few points to correct and relatively few errors made by the LLM,\nwhich freed up time for me to add specific business logic for a major client.\n\nFor the third network service, Shared Internet Access (SIA), even simpler than the previous two, I provided the LLM with\nboth the DIA and PVLAN functions. With the magic word *“extrapolation”* in the prompt, the generated code was 95%\ncorrect.\n\n## Summary of My Experience\n\n- **DIA**: 0% AI, 100% handwritten code (business logic + GUI + overhaul of the switch configuration task management\nsystem) → **4 weeks**\n\n- **PVLAN**: 80% AI, 20% handwritten code (corrections + adding specific business logic) → **1 week**\n\n- **SIA**: 95% AI, 5% handwritten code (minor corrections) → **1 day**\n\nThe time saved was reinvested in testing, bug fixes, project management, and even a few additions outside the initial\nscope.\n\nMoreover, the same app was used on the “Tour de France 2025” with minor changes that were made easily thanks to the\nhypermedia approach.\n\nThis result is possible because of the combination of *HTMX + the procedural approach*, which produces naturally\nreadable code, without unnecessary abstraction layers. The data flow is clear, concentrated in a single function, and\nthe GUI/server logic is directly accessible.\n\nFor an LLM, this is ideal: it doesn’t need to construct context through a complex architecture. It just needs to follow\nthe flow and extrapolate it to a new use case. In other words, what’s simpler for the developer is also simpler for the\nAI. This is the sense in which HTMX is truly *“AI friendly”™*.\n\nUltimately, HTMX mainly allowed me to save time and keep my code clear.\nNo unnecessary layers, no superfluous complexity: just concrete stuff that works, fast.\n\nAnd that has made a big difference on these critical projects.","publishedAt":"2026-01-16T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.807Z","url":"https://htmx.org/essays/paris-2024-olympics-htmx-network-automation/","media":[],"coverageCount":0},{"id":"rel_04N1H_VUvzPG6QtTxzWIK","version":null,"type":"feature","title":"The fetch()ening","summary":"![Stop trying to make fetch() happen](/img/fetch.png)\n\nOK, I said there would never be a version three of htmx.\n\nBut, *technically*, I never said anyt...","titleGenerated":null,"titleShort":null,"content":"![Stop trying to make fetch() happen](/img/fetch.png)\n\nOK, I said there would never be a version three of htmx.\n\nBut, *technically*, I never said anything about a version *four*…\n\n## htmx 4: The fetch()ening\n\nIn [The Future of htmx](https://htmx.org/essays/hypermedia-driven-applications/) I said the following:\n\nWe are going to work to ensure that htmx is extremely stable in both API & implementation. This means accepting and\ndocumenting the [quirks](https://htmx.org/quirks/) of the current implementation.\n\nEarlier this year, on a whim, I created [fixi.js](https://github.com/bigskysoftware/fixi), a hyperminimalist implementation\nof the ideas in htmx.  That work gave me a chance to get a lot more familiar  with the `fetch()` and, especially, the\n[async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) infrastructure\navailable in JavaScript.\n\nIn doing that work I began to wonder if that, while the htmx [API](https://htmx.org/reference/#attributes)\nis (at least reasonably) correct, maybe there was room for a more dramatic change of the implementation that took\nadvantage of these features in order to simplify the library.\n\nFurther, changing from ye olde [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)\n(a legacy of htmx 1.0 IE support) to [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) would\nbe a pretty violent change, guaranteed to break at least *some* stuff.\n\nSo I began thinking: if we are going to consider moving to fetch, then maybe we should *also* use this update as a\nchance address at least *some* of the [quirks & cruft](https://htmx.org/quirks/) that htmx has acquired over its lifetime.\n\nSo, eventually & reluctantly, I have changed my mind: there *will* be another major version of htmx.\n\nHowever, in order to keep my word that there will not be a htmx 3.0, the next release will instead be htmx 4.0.\n\n## Project Goals\n\nWith htmx 4.0 we are rebuilding the internals of htmx, based on the lessons learned from\nfixi.js and [five+ years](https://www.npmjs.com/package/htmx.org/v/0.0.1) of supporting htmx.\n\nThere are three major simplifying changes:\n\n### The fetch()ening\n\nThe biggest internal change is that `fetch()` will replace `XMLHttpRequest` as the core ajax infrastructure.  This\nwon’t actually have a huge effect on most usages of htmx *except* that the events model will necessarily change due\nto the differences between `fetch()` and `XMLHttpRequest`.\n\n### Explicit Inheritance By Default\n\nI feel that the biggest mistake in htmx 1.0 & 2.0 was making attribute inheritance implicit.  I was inspired by CSS in\ndoing this, and the results have been roughly the same as CSS: powerful & maddening.\n\nIn htmx 4.0, attribute inheritance will be explicit by default rather than implicit.  Explicit inheritance will\nbe done via the `:inherited` modifier:\n\n```\n  div hx-target:inherited=\"#output\">\n    button hx-post=\"/up\">Likebutton>\n    button hx-post=\"/down\">Dislikebutton>\n  div>\n  output id=\"output\">Pick a button...output>\n\n```\n\nHere the `hx-target` attribute is explicitly declared as `inherited` on the enclosing `div` and, if it wasn’t, the\n`button` elements would not inherit the target from it.\n\nYou will be able to revert to htmx 2.0 implicit inheritance behavior via a configuration variable.\n\n### No Locally Cached History\n\nAnother source of pain for both us and for htmx users is history support.  htmx 2.0 stores history in local\ncache to make navigation faster.  Unfortunately, snapshotting the DOM is often brittle because of third-party\nmodifications, hidden state, etc.  There is a terrible simplicity to the web 1.0 model of blowing everything away and\nstarting over.  There are also security concerns storing history information in session storage.\n\nIn htmx 2.0, we often end up recommending that people facing history-related issues simply disable the cache entirely,\nand that usually fixes the problems.\n\nIn htmx 4.0, history support will no longer snapshot the DOM and keep it locally.  It will, rather, issue a network\nrequest for the restored content.  This is the behavior of 2.0 on a history cache-miss, and it works reliably with\nlittle effort on behalf of htmx users.\n\nWe will offer an extension that enables history caching like in htmx 2.0, but it will be opt-in, rather than the default.\n\nThis tremendously simplifies the htmx codebase and should make the out-of-the-box behavior much more plug-and-play.\n\n## What Stays The Same?\n\nMost things.\n\nThe [core](https://dl.acm.org/doi/10.1145/3648188.3675127) functionality of htmx will remain the same, `hx-get`, `hx-post`,\n`hx-target`, `hx-boost`, `hx-swap`, `hx-trigger`, etc.\n\nWith a few configuration tweaks, most htmx 2.x based applications should work with htmx 4.x.\n\nThese changes will make the long term maintenance & sustainability of the project much stronger.  It will also take\npressure off the 2.0 releases, which can now focus on stability rather than contemplating new features.\n\n## Upgrading\n\nhtmx 2.0 users *will* face an upgrade project when moving to 4.0 in a way that they did not have to in moving\nfrom 1.0 to 2.0.\n\nI am sorry about that, and want to offer two things to address it:\n\n- htmx 2.0 (like htmx 1.0 & intercooler.js 1.0) will be supported *in perpetuity*, so there is absolutely *no* pressure to\nupgrade your application: if htmx 2.0 is satisfying your hypermedia needs, you can stick with it.\n\n- We will roll htmx 4.0 out slowly, over a multi-year period.  As with the htmx 1.0 -> 2.0 upgrade, there will be a long\nperiod where htmx 2.x is `latest` and htmx 4.x is `next`\n\n## New Features\n\nBeyond simplifying the implementation of htmx significantly, switching to fetch also gives us the opportunity to add\nsome nice new features to htmx\n\n### Streaming Responses & SSE in Core\n\nBy switching to `fetch()`, we can take advantage of its support for\n[readable streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams), which\nallow for a stream of content to be swapped into the DOM, rather than a single response.\n\nhtmx 1.0 had Server Sent Event support integrated into the library.  In htmx 2.0 we pulled this functionality out as an\nextension.  It turns out that SSE is just a specialized version of a streaming response, so in adding streaming\nsupport, it’s an almost-free free two-fer to add that back into core as well.\n\nThis will make incremental response swapping much cleaner and well-supported in htmx.\n\n### Morphing Swap in Core\n\n[Three years ago](https://github.com/bigskysoftware/idiomorph/commit/7760e89d9f198b43aa7d39cc4f940f606771f47b) I had\nan idea for a DOM morphing algorithm that improved on the initial algorithm pioneered by [morphdom](https://github.com/patrick-steele-idem/morphdom).\n\nThe idea was to use “id sets” to make smarter decisions regarding which nodes to preserve and which nodes to delete when\nmerging changes into the DOM, and I called this idea “idiomorph”.  Idiomorph has gone on to be adopted by many other\nweb project such as [Hotwire](https://hotwired.dev/).\n\nWe strongly considered including it in htmx 2.0, but I decided not too because it worked well as an extension and\nhtmx 2.0 had already grown larger than I wanted.\n\nIn 4.0, with the complexity savings we achieved by moving to `fetch()`, we can now comfortably fit a `morphInner` and\n`morphOuter` swap into core, thanks to the excellent work of Michael West.\n\n### Explicit  Tag Support\n\nhtmx has, since very early on, supported a concept of “Out-of-band” swaps: content that is removed from the main HTML\nresponse and swapped into the DOM elsewhere.  I have always been a bit ambivalent about them, because they move away\nfrom [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/), but there is no doubt that they are useful\nand often crucial for achieving certain UI patterns.\n\nOut-of-band swaps started off very simply: if you marked an element as `hx-swap-oob='true'`, htmx would swap the element\nas the outer HTML of any existing element already in the DOM with that id.  Easy-peasy.\n\nHowever, over time, people started asking for different functionality around Out-of-band swaps: prepending, appending,\netc. and the feature began acquiring some fairly baroque syntax to handle all these needs.\n\nWe have come to the conclusion that the problem is that there are really *two* use cases, both currently trying to be\nfilled by Out-of-band swaps:\n\n- A simple, id-based replacement\n\n- A more elaborate swap of partial content\n\nTherefore, we are introducing the notion of ``s in htmx 4.0\n\nA partial element is, under the covers, a template element and, thus, can contain any sort of content you like.  It\nspecifies on itself all the standard htmx options regarding swapping, `hx-target` and `hx-swap` in particular, allowing\nyou full access to all the standard swapping behavior of htmx without using a specialized syntax.  This tremendously\nsimplifies the mental model for these sorts of needs, and dovetails well with the streaming support we intend to offer.\n\nOut-of-band swaps will be retained in htmx 4.0, but will go back to their initial, simple focus of simply replacing\nan existing element by id.\n\n### Improved View Transitions Support\n\nhtmx 2.0 has had [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) support since\n[April of 2023](https://github.com/bigskysoftware/htmx/blob/master/CHANGELOG.md#190---2023-04-11).  In the interceding\ntwo years, support for the feature has grown across browsers (c’mon, safari, you can do it) and we’ve gained experience\nwith the feature.\n\nOne thing that has become apparent to us while using them is that, to use them in a stable manner, it is important\nto establish a *queue* of transitions, so each can complete before the other begins.  If you don’t do this, you can get\nvisually ugly transition cancellations.\n\nSo, in htmx 4.0 we have added this queue which will ensure that all view transitions complete smoothly.\n\nCSS transitions will continue to work as before as well, although the swapping model is again made much simpler by the\nasync runtime.\n\nWe may enable View Transitions by default, the jury is still out on that.\n\n### Stabilized Event Ordering\n\nA wonderful thing about `fetch()` and the async support in general is that it is *much* easier to guarantee a stable\norder of events.  By linearizing asynchronous code and allowing us to use standard language features like try/catch,\nthe event model of htmx should be much more predictable and comprehensible.\n\nWe are going to adopt a new standard for event naming to make things even clearer:\n\n`htmx::[:]`\n\nSo, for example, `htmx:before:request` will be triggered before a request is made.\n\n### Improved Extension Support\n\nAnother opportunity we have is to take advantage of the `async` behavior of `fetch()` for much better performance in our\npreload extension (where we issue a speculative (`GET` only!) request in anticipation of an actual trigger).  We have\nalso added an optimistic update extension to the core extensions, again made easy by the new async features.\n\nIn general, we have opened up the internals of the htmx request/response/swap cycle much more fully to extension developers,\nup to and including allowing them to replace the `fetch()` implementation used by htmx for a particular request.  There\nshould not be a need for any hacks to get the behavior you want out of htmx now: the events and the open “context” object\nshould provide the ability to do almost anything.\n\n### Improved `hx-on` Support\n\nIn htmx 2.0, I somewhat reluctantly added the [`hx-on`](https://htmx.org/attributes/hx-on/) attributes to support light\nscripting inline on elements.  I added this because HTML does not allow you to listen for arbitrary events via `on`\nattributes: only standard DOM events like `onclick` can be responded to.\n\nWe hemmed and hawed about the syntax and so, unfortunately, there are a few different ways to do it.\n\nIn htmx 4.0 we will adopt a single standard for the `hx-on` attributes: `hx-on:`.  Additionally, we are\nworking to improve the htmx JavaScript API (especially around async operation support) and will make those features\navailable in `hx-on`:\n\n```\nbutton hx-post=\"/like\"\n        hx-on:htmx:after:swap=\"await timeout('3s'); ctx.newContent[0].remove()\">\n    Get A Response Then Remove It 3 Seconds Later\nbutton>\n\n```\n\nhtmx will never support a fully featured scripting mechanism in core, we recommend something like\n[Alpine.js](https://alpinejs.dev/) for that, but our hope is that we can provide a relatively minimalist API that\nallows for easy, light async scripting of the DOM.\n\nI should note that htmx 4.0 will continue to work with `eval()` disabled, but you will need to forego a few features like\n`hx-on` if you choose to do so.\n\n### A Better But Familiar htmx\n\nAll in all, our hope is that htmx 4.0 will feel an awful lot like 2.0, but with better features and, we hope, with fewer bugs.\n\n## Timeline\n\nAs always, software takes as long as it takes.\n\nHowever, our current planned timeline is:\n\n- An alpha release is available *today*:  `htmx@4.0.0-alpha1`\n\n- A 4.0.0 release should be available in early-to-mid 2026\n\n- 4.0 will be marked `latest` in early-2027ish\n\nYou can track our progress (and see quite a bit of dust flying around) in the `four` branch on\n[github](https://github.com/bigskysoftware/htmx/tree/four) and at:\n\n[https://four.htmx.org](https://four.htmx.org)\n\nThank you for your patience and pardon our dust!\n\n“Well, when events change, I change my mind. What do you do?” –Paul Samuelson or John Maynard Keynes","publishedAt":"2025-11-01T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.807Z","url":"https://htmx.org/essays/the-fetchening/","media":[],"coverageCount":0},{"id":"rel_DJHMdxruV7ZoQ-doM6PPb","version":null,"type":"feature","title":"An interview with Leonard Richardson","summary":"Leonard Richardson is a long time programmer and author and was the creator of what came to be termed the Richardson\nMaturity\nModel ([https://en.wikip...","titleGenerated":null,"titleShort":null,"content":"Leonard Richardson is a long time programmer and author and was the creator of what came to be termed the Richardson\nMaturity\nModel ([https://en.wikipedia.org/wiki/Richardson_Maturity_Model](https://en.wikipedia.org/wiki/Richardson_Maturity_Model)),\na system for classifying Web APIs in terms of their adherence to REST. Here, Web APIs mean *data APIs*, that is data\nintended to be consumed by automated systems or code, rather than directly by a web client.\n\nThe RMM consists of four levels:\n\n- Level 0 - The Swamp of POX (Plain old XML)\n\n- Level 1 - The appropriate use of resource-based URLs\n\n- Level 2 - The appropriate use of HTTP Methods\n\n- Level 3 - Hypermedia Controls\n\nA Web API became more mature as it adopted these technologies and conventions.\n\nLeonard agreed to talk to me about the RMM and his experiences building Web software.\n\n**Question**: Can you give us some background about yourself and how you came into web programming? Did/do you consider\nyourself a hypermedia enthusiast?\n\nWhen I was in high school in the mid-1990s, I got very basic Internet access through BBSes. There were all these\namazing, arcane protocols you had to learn to get around: FTP, Gopher, Archie, NNTP, et cetera. And then just as I went\nto college, the World Wide Web suddenly wiped out all of those domain-specific protocols. Within a couple of years we\nwere using the Web technologies–URI, HTTP and HTML–for everything.\n\nMy formative years as a developer happened against the background of the incredible power of those three core\ntechnologies. Everything was being built on top of the Web, and it basically worked and was a lot simpler than the old\nway.\n\nSo yes, I am a hypermedia enthusiast, but it took me a really long time to understand the distinct advantages that come\nfrom the “hypermedia” part of the Web. And that came with an understanding of when those advantages are irrelevant or\nundesirable from a business standpoint.\n\n**Question**: Can you give us a brief history of early Web APIs? What was the origin story of the RMM?\n\nWhat we now call “web APIs” started as a reaction against SOAP, a technology from a time when corporate firewalls\nallowed HTTP connections (necessary to get work done) but blocked most other traffic (games?). SOAP let you serialize a\nprocedure call into XML and invoke it over an HTTP connection, punching through the firewall. It was an extraordinarily\nheavyweight solution, using the exact same tools–XML and HTTP–you’d need to make a good lightweight solution.\n\nNow that I’m an old fogey, I can look back on SOAP and see the previous generation of old fogeys trying to make the\n1990s client-server paradigm work over the Internet. SOAP had a lot of mindshare for a while, but there were very few\npublicly deployed SOAP services. When you deploy a service on the public Internet, people expect to connect to it from a\nwide variety of programming languages and programming environments. SOAP wasn’t cut out for that because it was so heavy\nand demanded so much tooling to compensate for its heaviness.\n\nInstead, developers picked and chose their designs from the core web technologies. Thanks to the dot-com boom, those\ntechnologies were understood by practicing developers and well supported by every major programming language.\n\nThe RMM, as it’s now called, was originally a heuristic I presented\nin [a talk in 2008](https://www.crummy.com/writing/speaking/2008-QCon/act3.html). [The first part of the talk](https://www.crummy.com/writing/speaking/2008-QCon/act1.html)\ngoes over the early history I mentioned earlier,\nand [the second part](https://www.crummy.com/writing/speaking/2008-QCon/act2.html) talks about my first experience\ntrying to sell hypermedia-based API design to an employer.\n\nI’d analyzed about a hundred web API designs for my book on REST and seen very strong groupings around the core web\ntechnologies. You’d see a lot of APIs that “got” URLs but didn’t “get” HTTP. But you’d never see one where it happened\nthe other way, an API that took advantage of the features of HTTP but didn’t know what to do with URLs. If I had one\ninsight here, it’s that the URL is the most fundamental web technology. HTTP is a set of rules for efficiently dealing\nwith URLs, and HTML (a.k.a. hypermedia) is a set of instructions for driving an HTTP client.\n\n**Question**: In “How Did REST come to mean the opposite of REST?” I assert that the term REST has nearly inverted in\nits meaning. In particular, I claim that most APIs stopped at “Level 2” of the RMM. Do you agree with these claims?\n\nEveryone understands URIs these days, and understanding HTTP is essential for performance reasons if nothing else. That\ngets you to level 2, and yes, there we have stayed. That’s what I was getting at\nin [this interview from 2007](https://www.infoq.com/articles/richardson-ruby-restful-ws/), a year before I gave my\nfamous talk:\n\nThe big question in my mind is whether architectures consciously designed with REST in mind will “win” over\narchitectures that are simple but only intermittently RESTful.\n\nYou don’t get a certificate signed by Roy Fielding for achieving level 3. The reward for learning and applying the\nlesson of hypermedia is *interoperability*. Your users get the ability to use your system in ways you didn’t anticipate,\nand they get to combine your system with their other systems.\n\nInteroperability is essential in a situation like the Web, where there are millions of server and client deployments,\nand a dozen major server implementations. (There are now only two major client implementations, but that’s its own sad\nstory.)\n\nFor a long time I thought people just didn’t get this and if I hammered on the technical advantages of hypermedia they’d\ncome around. But we’ve been stuck at level 2 for more than half the lifetime of the Web. It’s become clear to me that\nmost situations aren’t like the Web, and the advantages of hypermedia aren’t relevant to most business models.\n\n**Question**: Level 3 style hypermedia controls never really took off in Web APIs. Why do you think that is?\n\nI don’t do this for everything, but I am going to blame this one on capitalism.\n\nAlmost all actually deployed web APIs are under the complete control of one company. They have one server implementation\nwritten by that company and one server deployment managed by that company. If the API has any official client\nimplementations, those are also controlled by the company that owns the API. The fact that we say “the [company] API”\nis the opposite of interoperability.\n\nUsers like interoperability, but vendors prefer lock-in. We see that in their behavior. Netflix was happy to provide a\nhypermedia API for their program data… until their streaming business became big enough. Once they were the dominant\nplayer and could dictate the terms of integration, Netflix shut down their API. Twitter used to cut off your API access\nif your client got too popular; then they banned third-party clients altogether.\n\nThere are lots of APIs that consider interoperability a strong point, and many of them are oriented around hypermedia,\nbut almost all of them live outside the space of commercial competition. In 2008 when I gave the “maturity heuristic”\ntalk I was working on software development tools at Canonical, which is a for-profit company but heavily involved in\nopen source development. We wanted lots of tools and programming environments to be able to talk to our API, but the API\nwas a relatively small part of our process and we didn’t have enough developers to manage a bunch of official clients. A\nhypermedia-based approach gave us a certain flexibility to change our API without breaking all the clients.\n\nAfter that I spent eight years working on ebook delivery in the US public library space, which is extremely fragmented\nin terms of IT management. In a nonprofit environment with lots of independent server deployments, hypermedia (in the\nform of the [OPDS](https://opds.io/) protocol) was a really easy\npitch. [I gave a talk about that.](https://www.crummy.com/writing/speaking/2015-RESTFest/)\n\nTo get the benefits of hypermedia you have to collaborate with other people in the same field, consider the entire\nproblem space, and come up with a design that works for everyone. Who’s going to go through all that work when the\nreward is “no vendor lock-in”? People who are not competing with their peers: scientists, librarians, and open source\ndevelopers.\n\nIt might or might not surprise you to learn that the library world is dominated by an antique protocol\ncalled [SIP](https://developers.exlibrisgroup.com/wp-content/uploads/2020/01/3M-Standard-Interchange-Protocol-Version-2.00.pdf). (\nNot the VoIP protocol, a different SIP.) SIP is what the self-checkout machine uses to record the fact that you borrowed\nthe book. SIP first showed up in 1993, its design is distinctively non-RESTful, and in many ways it’s simply *bad*.\nEvery library vendor has come up with their own level 2 “REST” protocol to do what SIP does. But none have succeeded in\ndisplacing SIP, because that awful old 1993 design provides something a vendor can’t offer: interoperability between\ncomponents from different\nvendors. [I gave a talk about that, too.](https://www.crummy.com/writing/speaking/2016-RESTFest/)\n\n**Question**: Do you think the move from XML to JSON as a format had any influence on how Web APIs evolved?\n\nAbsolutely. Moving from XML to JSON replaced a document-centric design (more suitable for communications with a human at\none end) with a data-centric design (more suitable for machine-to-machine communication). The cost was forgetting about\nhypermedia altogether.\n\nOne thing about Martin’s diagram that I think obscures more than it reveals is: he calls level 0 the “Swamp of POX”.\nThis makes it seem like the problem is (Plain Old) XML. Martin is actually talking about SOAP there. The big problem\nwith SOAP services isn’t XML (although they do have way too much XML), it’s that they don’t use URLs. A SOAP client puts\nall of the request information into an XML package and tunnels it through a single service endpoint. This makes SOAP\nopaque to the tools designed to manage and monitor and inspect HTTP traffic. This is by design, because the point of\nSOAP is to let you make RPC calls when your IT department has everything but port 80 locked down.\n\nAnyway, XML is great! It’s too verbose to make an efficient data representation format, but XML has namespaces, and\nthrough namespaces it has hypermedia controls (via XLink, XForms, XHTML, Atom, etc.). JSON has no hypermedia controls,\nand because it also has no namespaces, you can’t add them after the fact.\n\nPeople started adopting JSON because they were tired of XML processing and excited about AJAX (in-browser HTTP clients\ndriven by Javascript, for those who weren’t there). But that initial decision started constraining decisions down the\nroad.\n\nBy 2011, all new web APIs were using a representation format with no hypermedia controls. You couldn’t do a\nhypermedia-based design if you wanted to. Our technical language had lost the words. First you’d have to define a\nJSON-based media type that had hypermedia (like Siren), or namespaces (like JSON-LD).\n\n**Question**: What are your thoughts on GraphQL and other non-RESTful API technologies?\n\nWith regard to non-RESTful API technologies in general, I would suggest that folks take a break\nfrom [chapter 5](https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) of Roy Fielding’s dissertation,\nand look at chapters [2](https://ics.uci.edu/~fielding/pubs/dissertation/net_app_arch.htm%20)\nand [4](https://ics.uci.edu/~fielding/pubs/dissertation/web_arch_domain.htm).\n\nChapter 5 is where Fielding talks about the design of the Web, but Chapter 2 breaks down all of the possible good things\nyou might want from a networked application architecture, only some of which apply to the Web. Chapter 4 explains the\ntradeoffs that were made when designing the Web, giving us some good things at the expense of others.\n\nChapter 4 lists five main advantages of the Web: low entry-barrier, extensibility, distributed hypermedia, anarchic\nscalability, and independent deployment. REST is a really effective way of getting those advantages, but the advantages\nthemselves are what you really want. If you can get them without the Web technologies, then all you’ve lost is the\naccumulated expertise that comes with those technologies (although that ain’t nothing at this point). And if you *don’t*\nwant some of these advantages (probably distributed hypermedia) you can go back to chapter 2, start the process over,\nand end up with a differently optimized architecture.\n\nI don’t have any direct experience with GraphQL, though I’m about to get some at my current job, so take this with a\ngrain of salt:\n\nOn a technical level, GraphQL is solving a problem that’s very common in API design: performing a database query across\na network connection without sending a bunch of unneeded data over the wire. Looking at the docs I see it also has “\nMutations“ which seem very SOAP-ish. I guess I’d say GraphQL looks like a modern version of SOAP, optimized for the\ncommon case of querying a database.\n\nSince GraphQL is independently deployable, supports multiple server implementations and defines no domain-specific\nsemantics, an interoperable domain-specific API could be built on top of it. Rather than exporting your data model to\nGraphQL and clashing with a dozen similar data models from the same industry, you could get together with your peers and\nagree upon a common set of semantics and mutations for your problem space. Then you’d have interoperability. It’s not\nmuch different from what we did with OPDS in the library world, defining what concepts like “bookshelf” and “borrow”\nmean.\n\nWould it be RESTful? Nope! But again I’ll come back to SIP, the integration protocol that public libraries use to keep\ntrack of loans. SIP is a level zero protocol! It doesn’t use any of the Web technologies at all! But it provides\narchitectural properties that libraries value and vendor-centric solutions can’t offer–mainly interoperability–so it\nsticks around despite the presence of “RESTful” solutions.","publishedAt":"2025-02-19T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.807Z","url":"https://htmx.org/essays/interviews/leonard-richardson/","media":[],"coverageCount":0},{"id":"rel_lLTApNA5e62cVsAilfEPg","version":null,"type":"feature","title":"Vendoring","summary":"“Vendoring” software is a technique where you copy the source of another project directly into your own project.\n\nIt is an old technique that has been...","titleGenerated":null,"titleShort":null,"content":"“Vendoring” software is a technique where you copy the source of another project directly into your own project.\n\nIt is an old technique that has been used for time immemorial in software development, but the term “vendoring” to\ndescribe it appears to have originated in the [ruby community](https://stackoverflow.com/posts/72115282/revisions).\n\nVendoring can be and is still used today. You can vendor htmx, for example, quite easily.\n\nAssuming you have a `/js/vendor` directory in your project, you can just download the source into your own project like\nso:\n\n```\ncurl https://raw.githubusercontent.com/bigskysoftware/htmx/refs/tags/v2.0.4/dist/htmx.min.js > /js/vendor/htmx-2.0.4.min.js\n\n```\n\nYou then include the library in your `head` tag:\n\n```\nscript src=\"/js/vendor/htmx-2.0.4.min.js\">script>\n\n```\n\nAnd then you check the htmx source into your own source control repository.  (I would even recommend considering using\nthe [non-minimized version](https://raw.githubusercontent.com/bigskysoftware/htmx/refs/tags/v2.0.4/dist/htmx.js), so\nyou can better understand and debug the code.)\n\nThat’s it, that’s vendoring.\n\n## Vendoring Strengths\n\nOK, great, so what are some strengths of vendoring libraries like this?\n\nIt turns out there are quite a few:\n\n- Your entire project is checked in to your source repository, so no external systems beyond your source control need\nto be involved when building it\n\n- Vendoring dramatically improves dependency *visibility*: you can *see* all the code your project depends on, so you\nwon’t have a situation like we have in htmx, where we feel like we only have a few development dependencies, when in\nfact we may have a lot\n\n- This also means if you have a good debugger, you can step into the library code as easily as any other code.  You\ncan also read it, learn from it and even modify it if necessary.\n\n- From a security perspective, you aren’t relying on opaque code.  Even if your package manager has\nan integrity hash system, the actual code may be opaque to you.  With vendored code it is checked in and can be\nanalysed automatically or by a security team.\n\n- Personally, it has always seemed crazy to me that people will often resolve dependencies at deployment time, right\nwhen your software is about to go out the door.  If that bothers you, like it does me, vendoring puts a stop to it.\n\nOn the other hand, vendoring also has one massive drawback: there typically isn’t a good way to deal with what is called\nthe [transitive dependency](https://en.wikipedia.org/wiki/Transitive_closure) problem.\n\nIf htmx had sub-dependencies, that is, other libraries that it depended on, then to vendor it properly you would have to\nstart vendoring all those libraries as well.  And if those dependencies had further dependencies, you’d need to install\nthem as well… And on and on.\n\nWorse, two dependencies might depend on the same library, and you’ll need to make sure you get the\n[correct version](https://en.wikipedia.org/wiki/Dependency_hell) of that library for everything to work.\n\nThis can get pretty difficult to deal with, but I want to make a paradoxical claim that this weakness (and, again, it’s\na real one) is actually a strength in some way:\n\nBecause dealing with large numbers of dependencies is difficult, vendoring encourages a culture of *independence*.\n\nYou get more of what you make easy, and if you make dependencies easy, you get more of them.  Making dependencies,\n*especially* transitive dependencies, more difficult would make them less common.\n\nAnd, as we will see in a bit, maybe fewer dependencies isn’t such a bad thing.\n\n## Dependency Managers\n\nThat’s great and all, but there are [significant](https://gist.github.com/datagrok/8577287)\n[drawbacks](https://web.archive.org/web/20180216205752/http://blog.bithound.io/why-we-stopped-vendoring-our-npm-dependencies/)\nto vendoring, particular the transitive dependency problem.\n\nModern software engineering uses dependency managers to deal with the dependencies of software projects.  These tools\nallow you to specify your projects dependencies, typically via some sort of file.  They then they will install those\ndependencies and resolve and manage all the other dependencies that are necessary for those dependencies to work.\n\nOne of the most widely used package managers is NPM: The [Node Package Manager](https://www.npmjs.com/).  Despite having\nno runtime dependencies, htmx uses NPM to specify 16 development dependencies.  Development dependencies are dependencies\nthat are necessary for development of htmx, but not for running it.  You can see the dependencies at the bottom of\nthe NPM [`package.json`](https://github.com/bigskysoftware/htmx/blob/master/package.json) file for the project.\n\nDependency managers are a crucial part of modern software development and many developers today couldn’t imagine\nwriting software without them.\n\n### The Trouble with Dependency Managers\n\nSo dependency managers solve the transitive dependency problem that vendoring has.  But, as with everything in software\nengineering, there are tradeoffs associated with them.  To see some of these tradeoffs, let’s take a look at the\n[`package-lock.json`](https://github.com/bigskysoftware/htmx/blob/master/package-lock.json) file in htmx.\n\nNPM generates a `package-lock.json` file that contains the resolved transitive closure of dependencies for a project, with\nthe concrete versions of those dependencies.  This helps ensure that the same dependencies are used unless a user\nexplicitly updates them.\n\nIf you take a look at the `package-lock.json` for htmx, you will find that the original 13 development dependencies have\nballooned into a total of 411 dependencies when all is said and done.\n\nhtmx, it turns out, relies on a huge number of packages, despite priding itself on being a relatively lean.  In fact,\nthe `node_modules` folder in htmx is a whopping 110 megabytes!\n\nBut, beyond this bloat there are deeper problems lurking in that mass of dependencies.\n\nWhile writing this essay I found that htmx apparently depends on the\n[`array.prototype.findlastindex`](https://www.npmjs.com/package/array.prototype.findlastindex), a\n[polyfill](https://en.wikipedia.org/wiki/Polyfill_(programming)) for a JavaScript feature introduced in\n[2022](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex).\n\nNow, [htmx 1.x](https://v1.htmx.org/) is IE compatible, and I don’t *want* polyfills for *anything*: I want to write\ncode that will work in IE without any additional library support.  And yet a polyfill has snuck in via a chain\nof dependencies (htmx does not directly rely on it) that introduces a dangerous polyfill that would let me write\ncode that would break in IE, as well as other older browsers.\n\nThis polyfill may or may not be available when I run the htmx [test suite](https://htmx.org/test/) (it’s hard to tell)\nbut that’s the point: some dangerous code has snuck into my project without me even knowing it, due to the number\nand complexity of the (development) dependencies it has.\n\nThis demonstrates significant *cultural* problem with dependency managers:\n\nThey tend to foster a culture of, well, dependency.\n\nA spectacular example of this was the infamous [left-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident),\nin which an engineer took down a widely used package and broke the build at companies like Facebook, PayPal, Netflix,\netc.\n\nThat was a relatively innocuous, although splashy, issue, but a more serious concern is\n[supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack), where a hostile entity is able to compromise\na company via code injected unwittingly via dependencies.\n\nThe larger our dependency graph gets, the worse these problems get.\n\n## Dependencies Reconsidered\n\nI’m not the only person thinking about our culture of dependency.  Here’s what some other, smarter folks have to say\nabout it:\n\n[Armin Ronacher](https://x.com/mitsuhiko), creator of [flask](https://flask.palletsprojects.com/en/stable/)\nrecently said this on [the ol’twits](https://x.com/mitsuhiko/status/1882739157120041156):\n\nThe more I build software, the more I despise dependencies. I greatly prefer people copy/pasting stuff into their own\ncode bases or re-implement it. Unfortunately the vibe of the time does not embrace that idea much. I need that vibe\nshift.\n\nHe also wrote a great blog post about his\n[experience with package management](https://lucumr.pocoo.org/2025/1/24/build-it-yourself/) in the Rust ecosystem:\n\nIt’s time to have a new perspective: we should give kudos to engineers who write a small function themselves instead\nof hooking in a transitive web of crates. We should be suspicious of big crate graphs. Celebrated are the minimal\ndependencies, the humble function that just quietly does the job, the code that doesn’t need to be touched for years\nbecause it was done right once.\n\nPlease go read it in full.\n\nBack in 2021, [Tom Macwright](https://macwright.com) wrote this in\n[Vendor by default](https://macwright.com/2021/03/11/vendor-by-default)\n\nBut one thing that I do think is sort of unusual is: I’m vendoring a lot of stuff.\n\nVendoring, in the programming sense, means “copying the source code of another project into your project.” It’s in\ncontrast to the practice of using dependencies, which would be adding another project’s name to your package.json\nfile and having npm or yarn download and link it up for you.\n\nI highly recommend reading his take on vendoring as well.\n\n## Software Designed To Be Vendored\n\nSome good news, if you are an open source developer and like the idea of vendoring, is that there is a simple way to\nmake your software vendor-friendly: remove as many dependencies as you can.\n\n[DaisyUI](https://daisyui.com/), for example, has been in the process of\n[removing their dependencies](https://x.com/Saadeghi/status/1882556881253826941), going from 100 dependencies in\nversion 3 to 0 in version 5.\n\nThere is also a set htmx-adjacent projects that are taking vendoring seriously:\n\n- [Surreal](https://github.com/gnat/surreal) - a lightweight jQuery alternative\n\n- [Facet](https://github.com/kgscialdone/facet) - an HTML-oriented Web Component library\n\n- [fixi](https://github.com/bigskysoftware/fixi) - a minimal htmx alternative\n\nNone of these JavaScript projects are available in NPM, and all of them [recommend](https://github.com/gnat/surreal#-install)\n[vendoring](https://github.com/kgscialdone/facet#installation) the [software](https://github.com/bigskysoftware/fixi#installing)\ninto your own project as the primary installation mechanism.\n\n## Vendor First Dependency Managers?\n\nThe last thing I want to briefly mention is a technology that combines both vendoring and dependency management:\nvendor-first dependency managers.  I have never worked with one before, but I have been pointed to\n[vend](https://github.com/fosskers/vend), a common lisp vendor oriented package manager (with a great README), as well\nas [go’s vendoring option](https://go.dev/ref/mod#vendoring).\n\nIn writing this essay, I also came across [vendorpull](https://github.com/sourcemeta/vendorpull) and\n[git-vendor](https://github.com/brettlangdon/git-vendor), both of which are small but interesting projects.\n\nThese all look like excellent tools, and it seems to me that there is an opportunity for some of them (and tools like\nthem) to add additional functionality to address the traditional weaknesses of vendoring, for example:\n\n- Managing transitive dependencies, if any\n\n- Relatively easy updates of those dependencies\n\n- Managing local modifications made to dependencies (and maybe help manage contributing them upstream?)\n\nWith these additional features I wonder if vendor-first dependency managers could compete with “normal” dependency\nmanagers in modern software development, perhaps combining some of the benefits of both approaches.\n\nRegardless, I hope that this essay has helped you think a bit more about dependencies and perhaps planted the idea that\nmaybe your software could be a little less, well, dependent on dependencies.","publishedAt":"2025-01-27T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.859Z","url":"https://htmx.org/essays/vendoring/","media":[],"coverageCount":0},{"id":"rel__yEyI8jN8pXmZdgWPFE6_","version":null,"type":"feature","title":"An interview with Makinde Adeagbo, Creator of Primer","summary":"I’m delighted to be able to interview Makinde Adeagbo, one of the creators of [Primer](https://www.youtube.com/watch?v=wHlyLEPtL9o),\nan hypermedia-ori...","titleGenerated":null,"titleShort":null,"content":"I’m delighted to be able to interview Makinde Adeagbo, one of the creators of [Primer](https://www.youtube.com/watch?v=wHlyLEPtL9o),\nan hypermedia-oriented javascript library that was being used at Facebook in the 2000s.\n\nThank you for agreeing to an interview!\n\nQ: To begin with, why don’t you give the readers a bit of your background both professionally & technically?\n\nI’ve always been into tech. In high school, I used to build computers for friends and family. I took the computer science classes my high school offered and went on to study computer science in college. I was always amazed by the fact that I could build cool things—games, tools, etc.—with just a computer and an internet connection.\n\nI was lucky enough to participate in Explore Microsoft, an internship that identifies underrepresented college freshmen and gives them a shot at working at Microsoft. After that experience, I was sold on software as my future. I later interned at Apple and Microsoft again. During college, I also worked at Facebook when the company was about 150 employees. It was an incredible experience where engineers had near-total freedom to build and contribute to the company’s growth. It was exactly what I needed early in my career, and I thrived. From there, I went on to work at Dropbox and Pinterest and also co-founded the nonprofit, /dev/color.\n\nQ: Can you give me the history of how Primer came to be?\n\nIn 2010, the Facebook website was sloooow. This wasn’t the fault of any specific person—each engineer was adding features and, along the way, small amounts of JavaScript. However, we didn’t have a coherent system for sharing libraries or tracking how much JavaScript was being shipped with each page. Over time, this led to the 90th-percentile page load time ballooning to about 10 seconds! Midway through the year, reducing that load time by half became one of the company’s three top priorities. I was on a small team of engineers tasked with making it happen.\n\nAs we investigated where most of the JavaScript was coming from, we noticed the majority of it was performing simple tasks. These tasks involved either fetching additional data or markup from the server, or submitting a form and then receiving more markup to update the page. With limited time, we decided to build a small solution to abstract those patterns and reduce the amount of code needed on the page.\n\nTom Occhino and I built the first version of Primer and converted a few use cases ourselves to ensure it worked well. Once we were confident, we brought more engineers into the effort to scale it across the codebase.\n\nQ: Primer & React were both created at Facebook. Was there any internal competition or discussion between the teams? What did that look like?\n\nThe two projects came from different eras, needs, and parts of the codebase. As far as I know, there was never any competition between them.\n\nPrimer worked well for the type of website we were building in 2010. A key part of its success was understanding that it wasn’t meant to handle every use case. It was an 80/20 solution, and we didn’t use it for particularly complex interactions (like the interface for creating a new post).\n\nReact emerged from a completely different challenge: the ads tools. Managing, composing, and tracking hundreds of ads required a highly involved, complex interface. I’m not sure if they ever attempted to use Primer for it, but it would have been a miserable experience. We didn’t have the terminology at the time, but this was a classic example of a single-page application needing purpose-built tools. The users of that site also had a very different profile from someone browsing their home feed or clicking through photos.\n\nQ: Why do you think Primer ultimately failed at Facebook?\n\nI don’t think there’s any single technical solution that has spanned 15 years in Facebook’s platform. The site’s needs evolve, technology changes, and the internet’s landscape shifts over time. Primer served the site well for its time and constraints, but eventually, the product demanded richer interactivity, which wasn’t what Primer was designed for.\n\nOther tradeoffs also come into play: developer ease/speed, security, scalability. These priorities and tradeoffs change over time, especially as a company grows 10x in size.\n\nMore broadly, these things tend to work in cycles in the industry. Streamlined, fast solutions give way to richer, heavier tools, which eventually cycle back to streamlined and fast. I wouldn’t be surprised if something like Primer made a comeback at some point.\n\nQ: How much “theory” was there to Primer? Did you think much about hypermedia, REST, etc., when you were building it?\n\nNot much. Honestly, I was young and didn’t know a ton about the internet’s history or past research. I was drawn to the simplicity of the web’s underlying building blocks and thought it was fun to use those tools as they were designed. But, as always, the web is a layer cake of hacks and bandaids, so you have to be flexible.\n\nQ: What were the most important technical lessons you took away from Primer?\n\nHonestly, the biggest lessons were about people. Building a system like Primer is one thing, but for it to succeed, you have to train hundreds of engineers to use it. You have to teach them to think differently about building things, ask questions at the right time, and avoid going too far in the wrong direction. At the end of the day, even if the system is perfect, if engineers hate using it, it won’t succeed.","publishedAt":"2025-01-27T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.859Z","url":"https://htmx.org/essays/interviews/makinde-adeagbo/","media":[{"type":"video","url":"https://i.ytimg.com/vi/wHlyLEPtL9o/hqdefault.jpg","alt":"Makinde Adeagbo: Primer","linkUrl":"https://www.youtube.com/watch?v=wHlyLEPtL9o","r2Key":"releases/290b8722bb773c6d2b7cb245a22c556f93a4d1d37aceef75f01adf236c6da5b4.jpg","r2Url":"https://media.releases.sh/releases/290b8722bb773c6d2b7cb245a22c556f93a4d1d37aceef75f01adf236c6da5b4.jpg"}],"coverageCount":0},{"id":"rel_KbQf4FutzSUQI0h26Adr8","version":null,"type":"feature","title":"An interview with Mike Amundsen, Author of 'RESTful Web APIs'","summary":"Mike Amundsen is a computer programmer, author and speaker, and is one of the world leading experts on REST &\nhypermedia. He has been writing about RE...","titleGenerated":null,"titleShort":null,"content":"Mike Amundsen is a computer programmer, author and speaker, and is one of the world leading experts on REST &\nhypermedia. He has been writing about REST and Hypermedia since 2008 and has published two books on the ideas:\n\n- [RESTful Web APIs](http://restfulwebapis.com/)\n\n- [Building Hypermedia APIs with HTML and Node](http://www.dpbolvw.net/click-7269430-11260198?sid=HP&url=http%3A%2F%2Fshop.oreilly.com%2Fproduct%2F0636920020530.do%3Fcmp%3Daf-prog-book-product_cj_9781449306571_%25zp&cjsku=0636920020530)\n\nMike agreed to do an interview with me on his view of the history of hypermedia and where things are today.\n\n**Q**: The “standard” history of hypermedia is Vannevar Bush’s “As We May Think”, followed by Nelson introducing\nthe term “hypermedia” in 1963, Englebart’s “Mother of all Demos” in 1968 and then Berners-Lee creating The Web in 1990.\nAre there any other important points you see along the way?\n\nI think starting the history of what I call the “modern web” with Bush makes a lot of sense. Primarily because you can\ndirectly link Bush to Engelbart to Nelson to Berners-Lee to Fielding. That’s more than half a century of scholarship,\ndesign, and implementation that we can study, learn from, and expand upon.\n\nAt the same time, I think there is an unsung hero in the hypermedia story; one stretches back to the early 20th century.\nI am referring to the Belgian author and entrepreneur [Paul Otlet](https://en.wikipedia.org/wiki/Paul_Otlet). Otlet had\na vision of [a multimedia information system](https://monoskop.org/Mundaneum_symposium) he named the “World Wide\nNetwork”. He saw how we could combine text, audio, and video into a mix of live and on-demand replay of content from\naround the world. He even envisioned a kind of multimedia workstation that supported searching, storing, and playing\ncontent in what was the earliest instance I can find of an understanding of what we call “streaming services” today.\n\nTo back all this up, he\ncreated [a community of researchers](https://daily.jstor.org/internet-before-internet-paul-otlet/) that would read\nmonographs, articles, and books then summarize them to fit on a page or less. He then designed an identification\nsystem – much like our URI/URN/URLs today and created a massive card catalog system to enable searching and collating\nthe results into a package that could be shared – even by postal service – with recipients. He created web search by\nmail in the 1920s!\n\nThis was a man well ahead of his time that I’d like to see talked about more in hypermedia and information system\ncircles.\n\n**Question**: Why do you think that The Web won over other hypermedia systems (such as Xanadu)?\n\nThe short reason is, I think, that Xanadu was a much more detailed and specific way of thinking about linking documents,\ndocumenting provenance, and compensating authors. That’s a grand vision that was difficult to implement back in the 60s\nand 70s when Nelson was sharing his ideas.\n\nThere are, of course, lots of other factors. Berners-Lee’s vision was much smaller (he was trying to make it easy for\nCERN staff to share contact information!). Berners-Lee was, I think, much more pragmatic about the implementation\ndetails. He himself said he used existing tech (DNS, packet networking, etc.) to implement his ideas. That meant he\nattracted interest from lots of different communities (telephone, information systems, computing, networking, etc.).\n\nI would also say here that I wish [Wendy Hall](https://en.wikipedia.org/wiki/Wendy_Hall)\n’s [Microcosm](https://www.sciencefriday.com/articles/the-woman-who-linked-the-web-in-a-microcosm/) had gotten more\ntraction than it did. Hall and her colleagues built an incredibly rich hypermedia system in the 90s and released it\nbefore Berners-Lee’s version of “the Web” was available. And Hall’s Microcosm held more closely to the way Bush,\nEnglebart, and Nelson thought hypermedia systems would be implemented – primarily by storing the hyperlinks in a\nseparate “anchor document” instead of in the source document itself.\n\n**Question**: What do you think of my essay “How did REST come to mean the opposite of REST”? Are there any points you\ndisagree with in it?\n\nI read that piece back in 2022 when you released it and enjoyed it. While I have nothing to quibble with, really, there\nare a few observations I can share.\n\nI think I see most hypermedia developers/researchers go through a kind of cycle where you get exposed to “common” REST,\nthen later learn of “Fielding’s REST” and then go back to the “common REST” world with your gained knowledge and try to\nget others on board; usually with only a small bit of success.\n\nI know you like memes so, I’ll add mine here. This journey away from home, into expanded knowledge and the return to the\nmundane life you once led is – to me – just another example of Campbell’s Hero’s Journey. I feel this so strongly\nthat I created [my own Hero’s Journey presentation](http://amundsen.com/talks/2015-05-barcelona/index.html) to deliver\nat API conferences over the years.\n\nOn a more direct note. I think many readers of Fielding’s Dissertation (for those who actually read it) miss some key\npoints. Fielding’s paper is about designing network architecture, not about REST. REST is offered as a real-world\nexample but it is just that; an example of his approach to information network design. There have been other designs\nfrom the same school (UC Irvine) including Justin Erenkrantz’s Computational\nREST ([CREST](https://www.erenkrantz.com/CREST/)) and Rohit Kare’s Asynchronous REST (A-REST). These were efforts that\ngot the message of Fielding: “Let’s design networked software systems!”\n\nBut that is much more abstract work that most real-world developers need to deal with. They have to get code out the\ndoor and up and running quickly and consistently. Fielding’s work, he admitted, was on\nthe “[scale of decades](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-724)” – a scale\nmost developers are not paid to consider.\n\nIn the long run, I think it amazing that a PhD dissertation from almost a quarter-century ago has had such a strong\ninfluence on day-to-day developers. That’s pretty rare.\n\n**Question**: Hyperview, the mobile hypermedia that Adam Stepinski created, was very explicitly based on your books.\nHave you looked at his system?\n\nI have looked over [Hyperview](https://hyperview.org/) and like what I see. I must admit, however, that I don’t write\nmobile code anymore so I’ve not actually written any hyperview code myself. But I like it.\n\nI talked to Adam in 2022 about Hyperview in general and was impressed with his thoughts. I’d like to see more people\ntalking about and using the Hyperview approach.\n\nSomething I am pretty sure I mentioned to Adam at the time is that Hyperview reminds me of Wireless Markup\nLanguage ([WML](https://en.wikipedia.org/wiki/Wireless_Markup_Language)). This was another XML-based document model\naimed at rendering early web content on feature phones (before smartphone technology). Another XML-based hypermedia\ndomain-specific document format is [VoiceXML](https://en.wikipedia.org/wiki/VoiceXML). I still think there are great\napplications of hypermedia-based domain-specific markup languages (DSML) and would like to see more of them in use.\n\n**Question**: It’s perhaps wishful thinking, but I feel there is a resurgence in interest in the ideas of hypermedia and\nREST (real REST.)  Are you seeing this as well? Do you have a sense if businesses are starting to recognize the\nstrengths of this approach?\n\nI, myself, think there is a growth in hypermedia-inspired designs and implementations and I’m glad to see it. I think\nmuch of the work of APIs in general has been leading the market to start thinking about how to lower the barrier of\nentry for using and interoperating with remote, independent services. And the hypermedia control paradigm (the one you\nand your colleagues talk about in your\npaper “[Hypermedia Controls: Feral to Formal](https://dl.acm.org/doi/fullHtml/10.1145/3648188.3675127)”) offers an\nexcellent way to do that.\n\nI think the biggest hurdle for using more hypermedia in business is\nwas [laid out pretty conclusively](https://www.crummy.com/writing/speaking/2015-RESTFest/)\nby [Leonard Richardson](https://www.crummy.com/self/) several years ago. He helped build a\npowerful [hypermedia-based book-sharing server and client](https://opds.io/) system to support public libraries around\nthe world. He noted that, in the library domain, each site is not a competitor but a partner. That means libraries are\nencouraged to make it easier to loan out books and interoperate with other libraries.\n\nMost businesses operate on the opposite model. They typically succeed by creating barriers of entry and by hoarding\nassets, not sharing them. Hypermedia makes it easier to share and interact without the need of central control or other\ntypes of “gatekeeping.”\n\nHaving said that, I think a ripe territory for increased use of hypermedia to lower the bar and increase interaction is\nat the enterprise level in large organizations. Most big companies spend huge amounts of money building and rebuilding\ninterfaces in order to improve their internal information system. I can’t help but think designing and implementing\nhypermedia-driven solutions would yield long-term savings, and near-term sustainable interoperability.\n\n**Question**: Are there any concepts in hypermedia that you think we are sleeping on? Or, maybe said another way, some\nolder ideas that are worth looking at again?\n\nWell, as I just mentioned, I think hypermedia has a big role to play in the field of interoperability. And I think the\nAPI-era has, in some ways, distracted us from the power of hypermedia controls as a design element for\nservice-to-service interactions.\n\nWhile I think Nelson, Berners-Lee and others have done a great job of laying out the possibilities for human-to-machine\ninteraction, I think we’ve lost sight of the possibilities hypermedia gives us for machine-to-machine interactions. I am\nsurprised we don’t have more hypermedia-driven workflow systems available today.\n\nAnd I think the rise in popularity of LLM-driven automation is another great opportunity to create hypermedia-based,\ncomposable services that can be “orchestrated” on the fly. I am worried that we’ll get too tied up in trying to make\ngenerative AI systems look and act like human users and miss the chance to design hypermedia workflow designed\nspecifically to take advantage of the strengths of statistical language models.\n\nI’ve seen some interesting things in this area including [Zdenek Nemec](https://www.linkedin.com/in/zdne/)’s\n[Superface](https://superface.ai/) project which has been working on this hypermedia-driven workflow for several\nyears.\n\nI just think there are lots of opportunities to apply what we’ve learned from the last 100 years (when you include\nOtlet) of hypermedia thinking. And I’m looking forward to seeing what comes next.","publishedAt":"2025-01-27T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.859Z","url":"https://htmx.org/essays/interviews/mike-amundsen/","media":[],"coverageCount":0},{"id":"rel_CftCaObM6kFMo3gukGHgG","version":null,"type":"feature","title":"An interview with Chris Wanstrath aka @defunkt, Creator of pjax","summary":"I’m very excited to be able to interview @defunkt, the author of [pjax](https://github.com/defunkt/jquery-pjax), an\nearly hypermedia-oriented javascri...","titleGenerated":null,"titleShort":null,"content":"I’m very excited to be able to interview @defunkt, the author of [pjax](https://github.com/defunkt/jquery-pjax), an\nearly hypermedia-oriented javascript library that served as an inspiration for intercooler.js, which later became\nhtmx. He’s done a few other things too, like co-founding GitHub, but in this interview I want to focus on pjax, how it\ncame to be, what influenced it and what it in turn influenced.\n\nThank you for agreeing to an interview @defunkt!\n\nQ: To begin with, why don’t you give the readers a bit of your background both professionally & technically:\n\nI think I can sum up most of my technical background in two quick anecdotes:\n\n- \n\nFor “show and tell” in 6th grade, I brought in a printout of a web page I had made - including its source code. I\nlike to imagine that everyone was impressed.\n\n- \n\nRight after 7th grade, a bunch of rowdy high schoolers took me down to the local university during a Linux\ninstallfest and put Red Hat on my family’s old PC. That became my main computer for all of high school.\n\nSo pretty much from the start I was a web-slinging, UNIX-loving hippie.\n\nIn terms of coding, I started on QBasic using the IBM PC running OS/2 in my grandparents’ basement. Then I got deep into\nMUDs (and MUSHes and MUXes and MOOs…) which were written in C and usually included their own custom scripting\nlanguage. Writing C was “hardcoding”, writing scripts was “softcoding”. I had no idea what I was doing in C, but I\nreally liked the softcoding aspect.\n\nThe same rowdy high schoolers who introduced me to Linux gave me the O’Reilly camel book and told me to learn Perl. I\ndid not enjoy it. But they also showed me php3, and suddenly it all came together: HTML combined with MUD-like\nsoftcoding. I was hooked.\n\nI tried other things like ASP 3.0 and Visual Basic, but ultimately PHP was my jam for all of high school. I loved making\ndynamic webpages, and I loved Linux servers. My friends and I had a comedy website in high school that shall remain\nnameless, and I wrote the whole mysql/php backend myself before blogging software was popular. It was so much fun.\n\nMy first year of college I switched to Gentoo and became fascinated with their package manager, which was written in\nPython. You could write real Linux tools with it, which was amazing, but at the time the web story felt weak.\n\nI bought the huge Python O’Reilly book and was making my way through it when, randomly, I discovered Ruby on Rails. It\nhit me like a bolt of lightning and suddenly my PHP and Python days were over.\n\nAt the same time, Web 2.0 had just been coined and JavaScript was, like, “Hey, everyone. I’ve been here all along.” So\nas I was learning Rails, I was also learning JavaScript. Rails had helpers to abstract the JS away, but I actually\nreally liked the language (mostly) and wanted to learn it without relying on a framework or library.\n\nThe combination of administering my own Linux servers, writing backend code in Rails, and writing frontend code in\nJavaScript made me fall deeper in love with the web as a platform and exposed me to concepts like REST and HATEOAS.\nWhich, as someone who had been writing HTML for over a decade, felt natural and made sense.\n\nGitHub launched in 2008 powered by, surprise, Gentoo, Rails, and JavaScript. But due to GitHub’s position as not just a\nRails community, but a collection of programming communities, I quickly evolved into a massive polyglot.\n\nI went back and learned Python, competing in a few programming competitions like Django Dash and attending (and\nspeaking) at different PyCons. I learned Objective-C and made Mac (and later iPhone) apps. I learned Scheme and Lisp,\neventually switching to Emacs from Vim and writing tons of Emacs Lisp. I went back and learned what all the sigils mean\nin Perl. Then Lua, Java, C++, C, even C# - I wanted to try everything.\n\nAnd I’m still that way today. I’ve written projects in Go, Rust, Haskell, OCaml, F#, all sorts of Lisps (Chicken Scheme,\nClojure, Racket, Gambit), and more. I’ve written a dozen programming languages, including a few that can actually do\nsomething. Right now I’m learning Zig.\n\nBut I always go back to the web. It’s why I created the Atom text editor using web technologies, it’s why Electron\nexists, and it’s why I just cofounded the Ladybird Browser Initiative with Andreas Kling to develop the independent,\nopen source Ladybird web browser.\n\nQ: Can you give me the history of how pjax came to be?\n\nIt all starts with XMLHttpRequest, of course. Ajax. When I was growing up, walking to school both ways uphill in the\nsnow, the web was simple: you clicked on a link and a new web page loaded. Nothing fancy. It was a thing of beauty, and\nit was good.\n\nThen folks started building email clients and all sorts of application-like programs in HTML using `` and\nfriends. It was not very beautiful, and not very good, but there was something there.\n\nLuckily, in the mid-2000s, Gmail and Ajax changed things. Hotmail had been around for a while, but Gmail was fast. By\nupdating content without a full page load using XMLHttpRequest, you could make a webpage that felt like a desktop\napplication without resorting to frames or other chicanery. And while other sites had used Ajax before Gmail, Gmail\nbecame so popular that it really put this technique on the map.\n\nSoon Ajax, along with the ability to add rounded corners to web pages, ushered in the era known as Web 2.0. By 2010,\nmore and more web developers were pushing more and more of their code into JavaScript and loading dynamic content with\nAjax. There was just one problem: in the original, good model of the web, each page had a unique URL that you could use\nto load its content in any context. This is one of the innovations of the web. When using Ajax, however, the URL doesn’t\nchange. And even worse, it can’t be changed - not the part that gets read by the server, anyway. The web was broken.\n\nAs is tradition, developers created hacks to work around this limitation. The era of the #! began, pioneered by\nAjax-heavy sites like Facebook and Twitter. Instead of http://twitter.com/htmx_org, you’d\nsee http://twitter.com/#!/htmx_org in your browser’s URL bar when visiting someone’s profile. The # was traditionally\nused for anchor tags, to link to a sub-section within a full web page, and could be modified by JavaScript. These\nancient web 2.0 developers took advantage of #’s malleability and started using it to represent permanent content that\ncould be updated inline, much like a real URL. The only problem was that your server code never saw the # part of a URL\nwhen serving a request, so now you needed to start changing your backend architecture to make everything work.\n\nOh, and it was all very buggy. That was a problem too.\n\nAs an HTTP purist, I detested the #!. But I didn’t have a better way.\n\nTime passed and lo, a solution appeared. One magical day, the #!s quietly disappeared from Facebook, replaced by good\nold fashioned URLs. Had they abandoned Web 2.0? No… they had found a better way.\n\nThe `history.pushState()` function, along with its sibling `history.replaceState()`, had been recently added to all\nmajor web browsers. Facebook quickly took advantage of this new API to update the full URL in your browser whenever\nchanging content via Ajax, returning the web to its previous glory.\n\nAnd so there it was: the Missing Link.\n\nWe had our solution, but now a new problem: GitHub was not an SPA, and I didn’t want it to be one. By 2011 I had been\nwriting JavaScript for six years - more than enough time to know that too much JS is a terrible thing. The original\nGitHub Issue Tracker was a Gmail-style web application built entirely in JS, circa 2009. It was an awful experience for\nme, GitHub developers, and, ultimately, our users.\n\nThat said, I still believed Ajax could dramatically speed up a web page’s user interface and improve the overall\nexperience. I just didn’t want to do it by writing lots of, or any, JavaScript. I liked the simple request/response\nparadigm that the web was built on.\n\nThus, Pjax was born. It sped up GitHub’s UI by loading new pages via Ajax instead of full page loads, correctly updating\nURLs while not requiring any JS beyond the Pjax library itself. Our developers could just tag a link with `[data-pjax]`\nand our backend application would then automatically render a page’s content without any layout, quickly getting you\njust the data you need without asking the browser to reload any JS or CSS or HTML that didn’t need to change. It also (\nmostly) worked with the back button, just like regular web pages, and it had a JS API if you did need to dip into the\ndark side and write something custom.\n\nThe first commit to Pjax was Feb 26, 2011 and it was released publicly in late March 2011, after we had been using it to\npower GitHub.com for some time.\n\nQ: I recall it being a big deal in the rails community. Did the advent of turbolinks hurt adoption there?\n\nMy goal wasn’t really adoption of the library. If it was, I probably would have put in the work to decouple it from\njQuery. At the time, I was deep in building GitHub and wasn’t the best steward of my many existing open source projects.\n\nWhat I wanted instead was adoption of the idea - I wanted people to know about `pushState()`, and I wanted people to\nknow there were ways to build websites other than just doing everything by hand in JavaScript. Rendering pages in whole\nor in part on the server was still viable, and could be sped up using modern techniques.\n\nTurbolinks being created and integrated into Rails was amazing to see, and not entirely unsurprising. I was a huge fan\nof Sam Stephenson’s work even pre-GitHub, and we had very similiar ideas about HTTP and the web. Part of my thinking was\ninfluenced by him and the Rails community, and part of what drew me to the Rails community was the shared ideas around\nwhat’s great about the web.\n\nBesides being coupled to jQuery, pjax’s approach was quite limited. It was a simple library. I knew that other people\ncould take it further, and I’m glad they did.\n\nQ: How much “theory” was there to pjax? Did you think much about hypermedia, REST, etc. when you were building it? (\nI backed into the theory after I had built intercooler, curious how it went for you!)\n\nNot much. It started by appending `?pjax=1` to every request, but before release we switched it to send an `X-PJAX`\nheader instead. Very fancy.\n\nEarly GitHub developer Rick Olson (@technoweenie), also from the Rails community, was the person who introduced me to\nHATEOAS and drove that philosophy in GitHub’s API. So anything good about Pjax came from him and Josh Peek, another\nearly Rails-er.\n\nMy focus was mostly on the user experience, the developer experience, and trying to stick to what made the web great.\n\n- First commit: https://github.com/defunkt/jquery-pjax/commit/3efcc3c\n\n- X-PJAX: https://github.com/defunkt/jquery-pjax/commit/4367ec9","publishedAt":"2025-01-27T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.859Z","url":"https://htmx.org/essays/interviews/chris-wanstrath/","media":[],"coverageCount":0},{"id":"rel_AK66lyQO3x56_waBkvCdN","version":null,"type":"feature","title":"A Real World wasm to htmx Port","summary":"img, video {\n  max-width: 100%;\n  margin: 10px;\n}\n\nWhen I was in college, I wrote some customer service software that tied together some custom AI mod...","titleGenerated":null,"titleShort":null,"content":"img, video {\n  max-width: 100%;\n  margin: 10px;\n}\n\nWhen I was in college, I wrote some customer service software that tied together some custom AI models I trained, the OpenAI API, a database, and some social media APIs to make the first version of [Sidekick](https://sidekickai.co).\n\n## Led astray\n\nOver the next couple years I worked on adding more features and growing the user base. As a solo founder, I should have been focused on sales, marketing, and market discovery. Instead, as an engineer, I wanted to hand-craft the perfect web stack. I was firmly of the belief that the network gap between the frontend and the backend could be abstracted away, and I could make writing web apps as simple as writing native apps. Did this have anything to do with my business, product, or customers? Absolutely not, but as many technical founders do, I believed if I perfected the tech, the customers would materialize.\n\nMy design decisions were naive, but also reminiscent to what’s seen in industry today: I wanted the backend and frontend to share a language (Rust), I wanted compile-time checks across the network boundary, I wanted to write frontend code like it was an app (reactive), and I wanted nearly instant reload times. What I got out of it was a buggy mess.\n\nI had invented a system where simple rust functions can be tagged with a macro to generate a backend route and a frontend request function, so you can call the function like it was a standard function, and it would run on the backend. A true poor-mans GraphQL. My desire to write Rust on the frontend required I compile a WASM bundle. My desire for instant load times required isomorphic SSR. All of this complexity, for what was essentially a simple CRUD site.\n\n## A better way\n\nAt this point Sidekick has grown and it now has a codebase which is responsible for not-insignificant volumes of traffic each day. There was this point where I looked into HTMX, multi-page websites, and HATEOAS, and realized the Sidekick codebase, which had grown into ~36k lines spread over 8 different crates, could be folded into a single crate, a single binary that ran the backend, which generated the frontend on demand through templating, and that HTMX could suffice for all the interactivity we required.\n\nLarge refactors typically have a bad track record so we wrote a quick and dirty simplified version of part of the site to convince ourselves it could work. After sufficient convincing, we undertook a full rewrite. All said and done, the rewrite took approximately 3 weeks of intense work. The results were dramatic:\n\n- **36k LOC -> 8k LOC**\n\n- **8 crates -> 1 crate**\n\n- **~5 bug reports / week -> ~1 bug report / week**\n\n- **More full nights of sleep**\n\n![sidekick_port_loc.jpg](/img/sidekick_port_loc.jpg)\n\nThe rewrite went far better than I could have imagined. It definitely won’t be representative of every experience, our app was definitely uniquely suited to HTMX. Axum and some custom middleware also went a long way for sharing common infrastructure across the site. Though we don’t have proper metrics, we’ve anecdotally noticed significantly improved load times.\n\n## Reflection\n\nI’ll finish by touching on the biggest benefit in my eyes: it’s tremendously easier to add new features as our customers request them. A feature that would have taken 2 weeks to fully implement, test and ship, now takes a day or two. As a small startup with a large number of customer demands, this is table stakes.\n\nSidekick hasn’t raised VC funding so I can’t afford to hire lots of devs. With HTMX we don’t need to.","publishedAt":"2025-01-10T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.933Z","url":"https://htmx.org/essays/a-real-world-wasm-to-htmx-port/","media":[],"coverageCount":0},{"id":"rel_Wbcm6dARKwU2lNqGLRciJ","version":null,"type":"feature","title":"The future of htmx","summary":"## In The Beginning…\n\nhtmx began life as [intercooler.js](https://intercoolerjs.org), a library built around jQuery that added behavior based\non HTML ...","titleGenerated":null,"titleShort":null,"content":"## In The Beginning…\n\nhtmx began life as [intercooler.js](https://intercoolerjs.org), a library built around jQuery that added behavior based\non HTML attributes.\n\nFor developers who are not familiar with it, [jQuery](https://jquery.com/) is a venerable JavaScript\nlibrary that made writing cross-platform JavaScript a lot easier during a time when browser implementations were very\ninconsistent, and JavaScript didn’t have many of the convenient APIs and features that it does now.\n\nToday many web developers consider jQuery to be “legacy software.” With all due respect to this perspective, jQuery is\ncurrently used on [75% of all public websites](https://w3techs.com/technologies/overview/javascript_library), a number that dwarfs all other JavaScript tools.\n\nWhy has jQuery remained so ubiquitous?\n\nHere are three technical reasons we believe contribute to its ongoing success:\n\n- It is very easy to add to a project (just a single, dependency-free link)\n\n- It has maintained a very consistent API, remaining largely backwards compatible over its life (intercooler.js works\nwith jQuery v1, v2 and v3)\n\n- As a library, you can use as much or as little of it as you like: it stays out of the way otherwise and doesn’t\ndictate the structure of your application\n\n## htmx is the New jQuery\n\nNow, that’s a ridiculous (and arrogant) statement to make, of course, but it is an *ideal* that we on the htmx team are\nstriving for.\n\nIn particular, we want to emulate these technical characteristics of jQuery that make it such a low-cost, high-value\naddition to the toolkits of web developers. Alex has\ndiscussed [“Building The 100 Year Web Service”](https://www.youtube.com/watch?v=lASLZ9TgXyc) and we want htmx to be a\nuseful tool for exactly that use case.\n\nWebsites that are built with jQuery stay online for a very long time, and websites built with htmx should be capable of\nthe same (or better).\n\nGoing forward, htmx will be developed with its *existing* users in mind.\n\nIf you are an existing user of htmx—or are thinking about becoming one—here’s what that means.\n\n### Stability as a Feature\n\nWe are going to work to ensure that htmx is extremely stable in both API & implementation. This means accepting and\ndocumenting the [quirks](https://htmx.org/quirks/) of the current implementation.\n\nSomeone upgrading htmx (even from 1.x to 2.x) should expect things to continue working as before.\n\nWhere appropriate, we may add better configuration options, but we won’t change defaults.\n\n### No New Features as a Feature\n\nWe are going to be increasingly inclined to not accept new proposed features in the library core.\n\nPeople shouldn’t feel pressure to upgrade htmx over time unless there are specific bugs that they want fixed, and they\nshould feel comfortable that the htmx that they write in 2025 will look very similar to htmx they write in 2035 and\nbeyond.\n\nWe will consider new core features when new browser features become available, for example we\nare [already using](https://htmx.org/examples/move-before/) the experimental `moveBefore()` API on supported browsers.\n\nHowever, we expect most new functionality to be explored and delivered via the\nhtmx [extensions API](https://htmx.org/extensions/), and will work to make the extensions API more capable where\nappropriate.\n\n### Quarterly Releases\n\nOur release schedule is going to be roughly quarterly going forward.\n\nThere will be no death march upgrades associated with htmx, and there is no reason to monitor htmx releases for major\nfunctionality changes, just like with jQuery. If htmx 1.x is working fine for you, there is no reason to feel like you\nneed to move to 2.x.\n\n## Promoting Hypermedia\n\nhtmx does not aim to be a total solution for building web applications and services:\nit [generalizes hypermedia controls](https://dl.acm.org/doi/pdf/10.1145/3648188.3675127), and that’s roughly about it.\n\nThis means that a very important way to improve htmx — and one with lots of work remaining — is by helping improve the tools\nand techniques that people use *in conjunction* with htmx.\n\nDoing so makes htmx dramatically more useful *without* any changes to htmx itself.\n\n### Supporting Supplemental Tools\n\nWhile htmx gives you a few new tools in your HTML, it has no opinions about other important aspects of building your\nwebsites. A flagship feature of htmx is that it does not dictate what backend or database you use.\n\nhtmx is [compatible with lots of backends](https://htmx.org/essays/hypermedia-on-whatever-youd-like/), and we want to\nhelp make hypermedia-driven development work better for all of them.\n\nOne part of the hypermedia ecosystem that htmx has already helped improve is template engines. When\nwe [first wrote](https://htmx.org/essays/template-fragments/) about how “template fragments” make defining partial page\nreplacements much simpler, they were a relatively rare feature in template engines.\n\nNot only are fragments much more common now, that essay\nis [frequently](https://github.com/mitsuhiko/minijinja/issues/260) [cited](https://github.com/sponsfreixes/jinja2-fragments)\nas an inspiration for building the feature.\n\nThere are many other ways that the experience of writing hypermedia-based applications can be improved, and we will\nremain dedicated to identifying and promoting those efforts.\n\n### Writing, Research, and Standardization\n\nAlthough htmx will not be changing dramatically going forward, we will continue energetically evangelizing the ideas of\nhypermedia.\n\nIn particular, we are trying to push [the ideas](https://dl.acm.org/doi/pdf/10.1145/3648188.3675127) of htmx into the\nHTML standard itself, via the [Triptych project](https://alexanderpetros.com/triptych/). In an ideal world, htmx\nfunctionality disappears into the web platform itself.\n\nhtmx code written *today* will continue working forever, of course, but in the very long run perhaps there will be no\nneed to include the library to achieve [similar UI patterns](https://htmx.org/examples) via hypermedia.\n\n## Intercooler Was Right\n\nAt the [end of the intercooler docs](https://intercoolerjs.org/docs#philosophy), we said this:\n\nMany javascript projects are updated at a dizzying pace. Intercooler is not.\n\nThis is not because it is dead, but rather because it is (mostly) right: the basic idea is right, and the implementation\nat least right enough.\n\nThis means there will not be constant activity and churn on the project, but rather\na [stewardship](https://en.wikipedia.org/wiki/Stewardship_(theology)) relationship: the main goal now is to not screw\nit up. The documentation will be improved, tests will be added, small new declarative features will be added around the\nedges, but there will be no massive rewrite or constant updating. This is in contrast with the software industry in\ngeneral and the front end world in particular, which has comical levels of churn.\n\nIntercooler is a sturdy, reliable tool for web development.\n\nLeaving aside [the snark at the end of the third paragraph](https://www.youtube.com/watch?v=zGyAWH5btwY), this thinking\nis very much applicable to htmx. In fact, perhaps even more so since htmx is a standalone piece of software, benefiting\nfrom the experiences (and mistakes) of intercooler.js.\n\nWe hope to see htmx, in its own small way, join the likes of giants like jQuery as a sturdy and reliable tool for\nbuilding your 100 year web services.","publishedAt":"2025-01-01T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.933Z","url":"https://htmx.org/essays/future/","media":[{"type":"video","url":"https://i.ytimg.com/vi/lASLZ9TgXyc/hqdefault.jpg","alt":"Building the Hundred-Year Web Service with htmx - Alexander Petros","linkUrl":"https://www.youtube.com/watch?v=lASLZ9TgXyc","r2Key":"releases/c5168b64a570673b29fbe599e7b7540a69b4289afcee9b664b80ced5ffc95b1a.jpg","r2Url":"https://media.releases.sh/releases/c5168b64a570673b29fbe599e7b7540a69b4289afcee9b664b80ced5ffc95b1a.jpg"},{"type":"video","url":"https://i.ytimg.com/vi/zGyAWH5btwY/hqdefault.jpg","alt":"A Theory of Open Source Marketing by Carson Gross (HTMX) at Big Sky Dev Con 2024","linkUrl":"https://www.youtube.com/watch?v=zGyAWH5btwY","r2Key":"releases/fe5a2d15f8d597c505c911302d252e11554370ece82107b5fb4b484bd757d58b.jpg","r2Url":"https://media.releases.sh/releases/fe5a2d15f8d597c505c911302d252e11554370ece82107b5fb4b484bd757d58b.jpg"}],"coverageCount":0},{"id":"rel_t1zYJ-h6WgT47ayKJ_4OW","version":null,"type":"feature","title":"htmx quirks","summary":"This is a “quirks” page, based on [SQLite’s “Quirks, Caveats, and Gotchas In SQLite” page](https://www.sqlite.org/quirks.html).\n\n## Attribute Inherita...","titleGenerated":null,"titleShort":null,"content":"This is a “quirks” page, based on [SQLite’s “Quirks, Caveats, and Gotchas In SQLite” page](https://www.sqlite.org/quirks.html).\n\n## Attribute Inheritance\n\nMany attributes in htmx are [inherited](https://htmx.org/docs/#inheritance): child elements can receive behavior from attributes located\non parent elements.\n\nAs an example, here are two htmx-powered buttons that inherit their [target](https://htmx.org/attributes/hx-target/) from a parent\ndiv:\n\n```\ndiv hx-target=\"#output\">\n    button hx-post=\"/items/100/like\">Likebutton>\n    button hx-delete=\"/items/100\">Deletebutton>\ndiv>\noutput id=\"output\">output>\n\n```\n\nThis helps avoid repeating attributes, thus keeping code [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).\n\nOn the other hand, as the attributes get further away elements, you lose [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/)\nand it becomes more difficult to understand what an element is doing.\n\nIt is also possible to inadvertently change the behavior of elements by adding attributes to parents.\n\nSome people prefer to disable inheritance in htmx entirely, using the `htmx.config.disableInheritance`\n[configuration variable](https://htmx.org/docs/#config).\n\nHere is a `meta` tag configuration that does so:\n\n```\n  meta name=\"htmx-config\" content='{\"disableInheritance\":true}'>\n\n```\n\n## The Default Swap Strategy is `innerHTML`\n\nThe [`hx-swap`](https://htmx.org/attributes/hx-swap/) attribute allows you to control how a swap is performed.  The default strategy is\n`innerHTML`, that is, to place the response HTML content within the target element.\n\nMany people prefer to use the `outerHTML` strategy as the default instead.\n\nYou can change this behavior using the `htmx.config.defaultSwapStyle`\n[configuration variable](https://htmx.org/docs/#config).\n\nHere is a `meta` tag configuration that does so:\n\n```\n  meta name=\"htmx-config\" content='{\"defaultSwapStyle\":\"outerHTML\"}'>\n\n```\n\n## Targeting the `body` Always Performs an innerHTML Swap\n\nFor historical reasons, if you target the `body` element, htmx will\n[always perform an `innerHTML` swap](https://github.com/bigskysoftware/htmx/blob/fb78106dc6ef20d3dfa7e54aca20408c4e4336fc/src/htmx.js#L1696).\n\nThis means you cannot change attributes on the `body` tag via an htmx request.\n\n## By Default `4xx` & `5xx` Responses Do Not Swap\n\nhtmx has never swapped “error” status response codes (`400`s & `500`s) by default.\n\nThis behavior annoys some people, and some server frameworks, in particular, will return a `422 - Unprocessable Entity`\nresponse code to indicate that a form was not filled out properly.\n\nThis can be very confusing when it is first encountered.\n\nYou can configure the response behavior of htmx via the [`htmx:beforeSwap`](https://htmx.org/docs/#modifying_swapping_behavior_with_events)\nevent or [via the `htmx.config.responseHandling` config array](https://htmx.org/docs/#response-handling).\n\nHere is the default configuration:\n\n```\n{\n  \"responseHandling\": [\n    {\"code\":\"204\", \"swap\": false},\n    {\"code\":\"[23]..\", \"swap\": true},\n    {\"code\":\"[45]..\", \"swap\": false, \"error\":true},\n    {\"code\":\"...\", \"swap\": false}]\n}\n\n```\n\nNote that `204  No Content` also is not swapped.\n\nIf you want to swap everything regardless of response code, you can use this configuration:\n\n```\n{\n  \"responseHandling\": [\n    {\"code\":\"...\", \"swap\": true}]\n}\n\n```\n\nIf you want to specifically allow `422` responses to swap, you can use this configuration:\n\n```\n{\n  \"responseHandling\": [\n    {\"code\":\"422\", \"swap\": true},\n    {\"code\":\"204\", \"swap\": false},\n    {\"code\":\"[23]..\", \"swap\": true},\n    {\"code\":\"[45]..\", \"swap\": false, \"error\":true},\n    {\"code\":\"...\", \"swap\": false}]\n}\n\n```\n\nHere is a meta tag allowing all responses to swap:\n\n```\n  meta name=\"htmx-config\" content='{\"responseHandling\": [{\"code\":\"...\", \"swap\": true}]}'>\n\n```\n\n## `GET` Requests on Non-Form Elements Do Not Include Form Values by Default\n\nIf a non-form element makes a non-`GET` request (e.g. a `PUT` request) via htmx, the values of the enclosing form\nof that element (if any) [will be included in the request](https://htmx.org/docs/#parameters).\n\nHowever, if the element issues a `GET`, the values of an enclosing form will\n[not be included.](https://github.com/bigskysoftware/htmx/blob/fb78106dc6ef20d3dfa7e54aca20408c4e4336fc/src/htmx.js#L3525)\n\nIf you wish to include the values of the enclosing form when issuing an `GET` you can use the\n[`hx-include`](https://htmx.org/attributes/hx-include/) attribute like so:\n\n```\nbutton hx-get=\"/search\"\n        hx-include=\"closest form\">\n  Search\nbutton>\n\n```\n\n## History Can Be Tricky\n\nhtmx provides support for interacting with the browser’s [history](https://htmx.org/docs/#history).  This can be very powerful, but it\ncan also be tricky, particularly if you are using 3rd party JavaScript libraries that modify the DOM.\n\nThere can also be [security concerns](https://htmx.org/docs/#hx-history) when using htmx’s history support.\n\nMost of these issues can be solved by disabling any local history cache and simply issuing a server request when a\nuser navigates backwards in history, with the tradeoff that history navigation will be slower.\n\nHere is a meta tag that disables history caching:\n\n```\n  meta name=\"htmx-config\" content='{\"historyCacheSize\": 0}'>\n\n```\n\n## Some People Don’t Like `hx-boost`\n\n[`hx-boost`](https://htmx.org/attributes/hx-boost/) is an odd feature compared with most other aspects of htmx: it “magically” turns\nall anchor tags and forms into AJAX requests.\n\nThis can speed the feel of these interactions up, and also allows the forms and anchors to continue working when\n[JavaScript is disabled](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement), however it comes\nwith some tradeoffs:\n\n- The history issues mentioned above can show up\n\n- Only the body of the web page will be updated, so any styles and scripts in the new page `head` tag will be discarded\n\n- The global javascript scope is not refreshed, so it is possible to have strange interactions between pages.  For example\na global `let` may start failing because a symbol is already defined.\n\nSome members on the core htmx team feel that, due to these issues, as well as the fact that browsers have improved\nquite a bit in page navigation, it is best to avoid `hx-boost` and\n[just use unboosted links and forms](https://unplannedobsolescence.com/blog/less-htmx-is-more/).\n\nThere is no doubt that `hx-boost` is an odd-man out when compared to other htmx attributes and suffers from the dictum\nthat “If something magically works, then it can also magically break.”\n\nDespite this fact, I (Carson) still feel it is useful in many situations, and it is used on the [https://htmx.org](https://htmx.org)\nwebsite.\n\n## Loading htmx asynchronously is unreliable\n\nhtmx is designed to be loaded with a standard, blocking `` tag, not one that is a [module](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#module) or [deferred](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#defer).\nAlthough we make a [best-effort attempt](https://github.com/bigskysoftware/htmx/blob/7ae66f9b33a5d39ad4084b0697ea34a6bf559cda/src/htmx.js#L5039-L5058) to initialize htmx regardless of when in the document lifecycle the script is loaded, there are some use-cases that slip through the cracks, typically ones that involve bundling or AJAX insertion of htmx itself.\n\nOur [past attempts](https://github.com/bigskysoftware/htmx/pull/3365#issuecomment-3065080028) to close this gap have all lead to unacceptable regressions.\nTherefore, although htmx can be loaded asynchronously, do so at your own risk.\n\nKeep in mind, also, that if your DOM content loads before htmx does, all the htmx-provided functionality will be nonfunctional until htmx loads.\n[Prefetching](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/prefetch) (or even “regular” fetching) htmx before you need it is one possible way to resolve this problem.\n\n## The JavaScript API Is Not A Focus\n\nhtmx is a hypermedia-oriented front end library.  This means that htmx enhances HTML via\n[attributes](https://htmx.org/reference/#attributes) in the HTML , rather than providing an elaborate\nJavaScript API.\n\nThere *is* a [JavaScript API](https://htmx.org/reference/#api), but it is not a focus of the library and, in most cases,\nshould not be used heavily by htmx end users.\n\nIf you find yourself using it heavily, especially the [`htmx.ajax()`](https://htmx.org/api/#ajax) method, it may be\nworth asking yourself if there is a more htmx-ish approach to achieve what you are doing.","publishedAt":"2024-12-23T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.933Z","url":"https://htmx.org/quirks/","media":[],"coverageCount":0},{"id":"rel_ZIfavJoqwBVloIMsZdx6n","version":null,"type":"feature","title":"htmx lore","summary":"![I lied, I don](/img/i-lied.png)\n\nFor better or [for worse](https://x.com/IroncladDev/status/1866185587616596356), htmx has collected a lot of lore, ...","titleGenerated":null,"titleShort":null,"content":"![I lied, I don](/img/i-lied.png)\n\nFor better or [for worse](https://x.com/IroncladDev/status/1866185587616596356), htmx has collected a lot of lore, mainly around [the twitter account](https://twitter.com/htmx_org).\n\nHere are some explanations.\n\n## It’s So Over/We’re So Back\n\nA common set of phrases used by htmx enthusiasts when, for example, [@bunjavascript told me to delete my account](https://x.com/bunjavascript/status/1708557665268568412)\n\n## htmx CEO\n\nAt one point there was a hostile takeover attempt of the htmx CEO position and, in a desperate poison pill, I declared\neveryone CEO of htmx.\n\n[Turk](https://x.com/gitpush_gitpaid) created [https://htmx.ceo](https://htmx.ceo) if you want to register as a CEO.\n\nIf someone emails hr@bigsky.software asking if you are CEO of htmx, I will tell them yes.\n\nYou can put it on your LinkedIn, because it’s true.\n\n## Laser Eye Horse\n\nAt some point I photoshopped lasers onto a horse mask, as kind of an homage to [@horse_js](https://x.com/horse_js).\n\nFor some reason it stuck and now it’s the [official unofficial](https://swag.htmx.org/products/i-lied-sticker) mascot of htmx.\n\n## Spieltrieb\n\nSpieltrieb means “play instinct”, and is a big part of the [htmx vibe](https://x.com/search?q=spieltrieb%20from%3Ahtmx_org&src=typed_query).\n\n## Pickles\n\nAt some point someone (I think [@techsavvytravvy](https://x.com/techsavvytravvy)), generated [a grug AI image](https://x.com/htmx_org/status/1708697536587047142), and there\nwas a pickle smiling in a really bizarre way in it.\n\nSo we started riffing on pickles and now [there’s a shirt](https://swag.htmx.org/products/htmx-pickle-shirt).\n\nCry more, [drizzle](https://x.com/DrizzleORM/status/1757149983713665238).\n\n## XSS\n\nIn July 2023, when htmx first got popular, there was a\n[moral panic](https://x.com/htmx_org/status/1683607693246775297) around\n[cross site scripting](https://x.com/htmx_org/status/1683529221195571200).  I\n[may](https://x.com/htmx_org/status/1683607217499414531) have\n[overcooked](https://x.com/htmx_org/status/1683649190071791617) my\n[response](https://x.com/htmx_org/status/1683612179512057856) to\n[it](https://x.com/htmx_org/status/1683818711763877892).\n\n## Shut Up Warren\n\n[@WarrenInTheBuff](https://x.com/WarrenInTheBuff) is the king of twitter and we regularly fight with him.  This often\nends in someone saying [“shut up warren”](https://x.com/ThePrimeagen/status/1792564215749779515).\n\nYou can see the htmx website do this by going to [https://htmx.org?suw=true](https://htmx.org?suw=true)\n\n## Microsoft Purchase Rumor\n\nIn mid-January of 2024 I got really serious with the htmx twitter account and started [quote](https://x.com/htmx_org/status/1745930477825868044)\ntweeting [things](https://x.com/htmx_org/status/1745915394626351315)\n[about microsoft](https://x.com/htmx_org).  People started [worrying](https://x.com/SusSoftware/status/1746206195461878113).  I announced a [license change](https://x.com/htmx_org/status/1746736273728094323)\nto get people freaked out about a rug pull.\n\n[I then changed htmx to BSD0](https://x.com/htmx_org/status/1746880860723544211)\n\n[This is the offer](https://x.com/htmx_org/status/1746895016256328079) I got from microsoft (real).\n\n## (same thing)\n\nI believe that [this tweet](https://x.com/htmx_org/status/1672264927136952322) is the origin of the (same thing) meme\n\n## Stronger Together\n\nIn December 2023, I was trying to get some indonesian twitter users to take a look at htmx, so I created a\n[“Montana & Indonesia, Stronger Together!”](https://x.com/htmx_org/status/1734371865156563428) tweet w/an AI image.\n\nThis turned into a [whole series of tweets](https://x.com/search?q=%22stronger%20together%22%20from%3A%40htmx_org&src=typed_query&f=live).\n\n## Hinges\n\nSometimes I am accused of being “unhinged” but, in fact, [I own many hinges](https://x.com/search?q=from%3Ahtmx_org%20hinges&src=typed_query).\n\n## * library\n\nPeople often [call htmx a framework](https://htmx.org/essays/is-htmx-another-javascript-framework/), but it’s [a library](https://x.com/htmx_org/status/1848751101035827210)\n\n## “man”\n\nA common [one word response](https://x.com/search?q=%22man%22%20from%3Ahtmx_org&src=typed_query&f=live) when I don’t feel like\narguing with someone.\n\n## The Le Marquee d’\n\nIn December 2024, I [added a marquee tag](https://github.com/bigskysoftware/htmx/commit/2b88d967c19619281228d1bf5398751615bdf462) to\nthe htmx website and started using the honorific (sic) in my twitter title.\n\n## htmx sucks\n\nI wrote an essay called [htmx sucks](https://htmx.org/essays/htmx-sucks/) in which I criticize htmx (some valid, some tongue in\ncheek, most both.)  I also released [a mug](https://swag.htmx.org/products/htmx-sucks-mug) that I will often link to when people are criticizing htmx.\n\n## Jason Knight\n\nJason Knight [hates htmx](https://x.com/JasonKn99664124/status/1731555036864381251) and wrote a\n[great post](https://archive.is/rQrl7) about it.\n\nPlease don’t harass him, [I draw energy](https://x.com/htmx_org/status/1756476449693872635) from his posts.\n\n## Drop Downs\n\nIn July 2023, sparked by the accusation that htmx users could not create dropdowns, I did a deep-dive into web\ndrop down technology and [uncovered a bombshell](https://x.com/htmx_org/status/1684936514885869568)\n\n## “htmx is a front end library of peace”\n\nA phrase I will often [quote tweet](https://x.com/search?q=htmx%20is%20a%20front%20end%20library%20of%20peace%20from%3A%40htmx_org&src=typed_query&f=live)\nviolent htmx-related imagery with.\n\n## The Process ™\n\n[The Process™](https://x.com/htmx_org/status/1697651918858764375) is the mechanism by which people initially hostile\nto htmx come to be enlightened.\n\n## “that’s ridiculous”\n\nIn [June 2023](https://x.com/htmx_org/status/1807183339222405317), [@srasash](https://twitter.com/srasash) accused\nhtmx of being a government op, the first in many such increasingly ridiculous claims.  I typically quote-tweet these\nclaims and point out that [“that’s ridiculous”](https://x.com/search?q=%22that%27s%20ridiculous%22%20from%3A%40htmx_org&src=typed_query&f=live)\n\n## Grug\n\nI created [http://grugbrain.dev](http://grugbrain.dev).\n\n## The htmx/intercooler.js feud\n\nThe htmx & [intercooler.js](https://x.com/intercoolerjs) twitter accounts often fight with one another.  Sometimes its\njust me [switching back and forth](https://x.com/intercoolerjs/status/1859652045399355559), but two other people have\naccess to the intercooler account, so sometimes I have no idea who I am fighting with.\n\n## If Nothing Magically Works\n\nNothing [magically breaks](https://x.com/htmx_org/status/1729870461864226829).\n\n## /r/webdev\n\nI was very unfairly given [a lifetime ban](https://x.com/htmx_org/status/1719687461385691283) from\n[/r/webdev/](https://www.reddit.com/r/webdev/) for an\n[obviously satirical post](https://old.reddit.com/r/webdev/comments/17i0loi/anyone_heard_of_htmx/).  Even the term “htmx” is banned (or semi-banned) on that sub, so people now use\nthe [htmeggs](https://swag.htmx.org/products/htmeggs-shirt) instead.\n\n## “looking into this”\n\n[idk](https://x.com/search?q=%22idk%22%20from%3Ahtmx_org&src=typed_query&f=live), I just think [it’s funny](https://x.com/search?q=%22looking%20into%20this%22%20from%3Ahtmx_org&src=typed_query)\n\n## “Look at this nerd ragin’”\n\nA common phrase used to [mock people (including ourselves)](https://x.com/search?q=%22Look%20at%20this%20nerd%20ragin%27%22%20from%3Ahtmx_org&src=typed_query) with.\n\n## Joker/Bane/Skeletor/Thanos, etc.\n\nhtmx is a [villain](https://x.com/htmx_org/status/1651698199478796292) in the front-end world, I’m good w/that","publishedAt":"2024-12-17T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.933Z","url":"https://htmx.org/essays/lore/","media":[],"coverageCount":0},{"id":"rel_iQ4I7NJ_ipCBrExB7_7hi","version":null,"type":"feature","title":"Prefer If Statements To Polymorphism...","summary":"## Or, Watching Myself Lose My Mind In Real Time…\n\n“Invert, always invert.” –Carl Jacobi, by way of Charlie Munger\n\n- *[If Statements](https://x.com/h...","titleGenerated":null,"titleShort":null,"content":"## Or, Watching Myself Lose My Mind In Real Time…\n\n“Invert, always invert.” –Carl Jacobi, by way of Charlie Munger\n\n- *[If Statements](https://x.com/htmx_org/status/1843804410377535533)*\n\nprefer if statements to polymorphism\n\nwhenever you are tempted to create a class, ask yourself: “could this be an if statement instead?”\n\n- *[The Closed/Closed Principle](https://x.com/htmx_org/status/1843805753007845474)*\n\nIn grug-oriented programming, the closed–closed principle (CCP) states “software entities (classes, modules, functions, etc.) should be closed for extension, but also closed for modification”\n\nthey should just do something useful man\n\n- *[The Minimize Abstractions Principle](https://x.com/htmx_org/status/1843806270559793475)*\n\nThe Minimize Abstractions Principle (MAP) is a computer programming principle that states that “a module should\nminimize the number of abstractions it contains, both in API and in implementation.  Stop navel gazing nerd.”\n\n- *[The Try It Out Substitution Principle](https://x.com/htmx_org/status/1843807054970139139)*\n\nThe “Try It Out” Substitution Principle states that you should try something out and, if that doesn’t work, think about why, and substitute something else for it instead.\n\nIt is common to need to substitute multiple things to hit on the right thing eventually.\n\n- *[The Useful Stuff Principle](https://x.com/htmx_org/status/1843807769557909528)*\n\nThe Useful Stuff Principle states that entities must depend on useful stuff, not overly abstract nonsense. It states that  a high-level module can depend on a low-level module, because that’s how software works.\n\n- *[Dependencies](https://x.com/htmx_org/status/1843808113230860419)*\n\nThe The Existence of Dependencies Is Not An Excuse For Destroying The Codebase Principle states that the existence of dependencies is not an excuse for destroying the codebase\n\n- *[Abstraction Budget](https://x.com/htmx_org/status/1843821830207099007)*\n\nconsider giving your developers an abstraction budget\n\nwhen they exhaust that budget & ask for more, tell them they can have another abstraction when they remove an existing one\n\nwhen they complain, look for classes with the term “Factory”,  “Lookup” or “Visitor” in their names\n\n- *[Fewer Functions](https://x.com/htmx_org/status/1843822378352291914)*\n\nif your function is only called in one place, consider inlining it & reducing the total number of method signatures in your module, to help people better understand it\n\nstudies show longer methods have fewer bugs per line of code, so favor longer methods over many smaller methods\n\n- *[“God” Object](https://x.com/htmx_org/status/1843823231771521367)*\n\nconsider creating “God” objects that wrap a lot of functionality up in a single package\n\nconsumers of your API don’t want to learn 50 different classes to get something done, so give them a few that provide the core functionality of your module with minimal fuss\n\n- *[Copy & Paste Driven Development](https://x.com/htmx_org/status/1843827082687852706)*\n\ncopy&paste driven development is a development methodology where, when you need to reuse some code but in a slightly different manner, you copy & paste the code & then modify it to satisfy the new requirements\n\nthis contrasts with designing an elaborate object model, for example\n\n- *[Implementation Driven Development](https://x.com/htmx_org/status/1843828023747063866)*\n\nimplementation driven development is a development methodology where you first explore various implementations of your idea to determine the best one, then add tests for it\n\nno test code may be written without first having some implementation code to drive that test\n\n- *[Mixing Concerns](https://x.com/htmx_org/status/1843830823113634132)*\n\nMixing of Concerns is a design methodology whereby “concerns” of various kinds are mixed into a single code unit.  This improves locality in that code unit by placing all relevant logic within it.  An example of this is hypermedia, which mixes control & presentation information.\n\n- *[Macroservices Architecture](https://x.com/htmx_org/status/1843831529300267103)*\n\na macroservice architecture revolves around “macroservices”: network-deployed modules of code that provide a significant amount of functionality to the overall system\n\nby adopting a macroservice-based architecture you minimize deployment complexity & maximize resource utilization\n\n- *[Sorry](https://x.com/htmx_org/status/1844005320223539524)*\n\nkinda had a manic break last night my bad","publishedAt":"2024-12-07T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.933Z","url":"https://htmx.org/essays/prefer-if-statements/","media":[],"coverageCount":0},{"id":"rel_tvHMsn8pNg40iuB1tsEOg","version":null,"type":"feature","title":"Codin' Dirty","summary":"![quick-and-dirty](/img/quick-and-dirty.png)\n\n“Writing clean code is what you must do in order to call yourself a professional. There is no reasonable...","titleGenerated":null,"titleShort":null,"content":"![quick-and-dirty](/img/quick-and-dirty.png)\n\n“Writing clean code is what you must do in order to call yourself a professional. There is no reasonable excuse for\ndoing anything less than your best.” [Clean Code](https://www.goodreads.com/book/show/3735293-clean-code)\n\nIn this essay I want to talk about how I write code.  I am going to call my approach “codin’ dirty” because I often\ngo against the recommendations of [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882),\na popular approach to writing code.\n\nNow, I don’t really consider my code all that dirty: it’s a little gronky in places but for the most part I’m\nhappy with it and find it easy enough to maintain with reasonable levels of quality.\n\nI’m also *not* trying to convince *you* to code dirty with this essay.  Rather, I want to\nshow that it is possible to write reasonably successful software this way and, I hope, offer some balance around software\nmethodology discussions.\n\nI’ve been programming for a while now and I have seen a bunch of different approaches to building software work.  Some\npeople love Object-Oriented Programming (I like it), other very smart people hate it.  Some folks love the expressiveness\nof dynamic languages, other people hate it. Some people ship successfully while strictly following Test Driven Development,\nothers slap a few end-to-end tests on at the end of the project, and many people end up somewhere between these extremes.\n\nI’ve seen projects using all of these different approaches ship and maintain successful software.\n\nSo, again, my goal here is not to convince you that my way of coding is the only way, but rather to show you (particularly\nyounger developers, who are prone to being intimidated by terms like “Clean Code”) that you can have a successful\nprogramming career using a lot of different approaches, and that mine is one of them.\n\n## TLDR\n\nThree “dirty” coding practices I’m going to discuss in this essay are:\n\n- (Some) big functions are good, actually\n\n- Prefer integration tests to unit tests\n\n- Keep your class/interface/concept count down\n\nIf you want to skip the rest of the essay, that’s the takeaway.\n\n## I Like Big Functions\n\nI think that large functions are fine. In fact, I think that *some* big functions are usually a *good* thing in a codebase.\n\nThis is in contrast with Clean Code, which says:\n\n“The first rule of functions is that they should be small. The second rule of functions is that they should be\nsmaller than that.” [Clean Code](https://www.goodreads.com/book/show/3735293-clean-code)\n\nNow, it always depends on the type of work that I’m doing, of course, but I usually tend to organize my functions into the\nfollowing:\n\n- A few large “crux” functions, the real meat of the module.  I set no bound on the Lines of Code (LOC) of these functions,\nalthough I start to feel a little bad when they get larger than maybe 200-300 LOC.\n\n- A fair number of “support” functions, which tend to be in the 10-20 LOC range\n\n- A fair number of “utility” functions, which tend to be in the 5-10 LOC range\n\nAs an example of a “crux” function, consider the [`issueAjaxRequest()`](https://github.com/bigskysoftware/htmx/blob/7fc1d61b4fdbca486263eda79c3f31feb10af783/src/htmx.js#L4057)\nin [htmx](https://htmx.org).  This function is nearly 400 lines long!\n\nDefinitely not clean!\n\nHowever, in this function there is a lot of context to keep around, and it lays out a series of specific steps that must\nproceed in a fairly linear manner.  There isn’t any reuse to be found by splitting it up into other functions and I\nthink it would hurt the clarity (and also importantly for me, the debuggability) of the function if I did so.\n\n### Important Things Should Be Big\n\nA big reason I like big functions is that I think that in software, all other things being equal, important things should\nbe big, whereas unimportant things should be little.\n\nConsider a visual representation of “Clean” code versus “Dirty” code:\n\n![clean-v-dirty.png](/img/clean-v-dirty.png)\n\nWhen you split your functions into many equally sized, small implementations you end up smearing the important parts of your\nimplementation around your module, even if they are expressed perfectly well in a larger function.\n\nEverything ends up looking the same: a function signature definition, followed by an if statement or a for loop, maybe a function\ncall or two, and a return.\n\nIf you allow your important “crux” functions to be larger it is easier to pick them out from the sea of functions, they\nare obviously important: just look at them, they are big!\n\nThere are also fewer functions in general in all categories, since much of the code has been merged into larger functions.\nFewer lines of code are dedicated to particular type signatures (which can change over time) and it easier to keep the\nimportant and maybe even the medium-important function names and signatures in your head.  You also tend to have fewer\nLOC overall when you do this.\n\nI prefer coming into a new “dirty” code module: I will be able to understand it more quickly and will remember the\nimportant parts more easily.\n\n### Empirical Evidence\n\nWhat about the empirical (dread word in software!) evidence for the ideal function size?\n\nIn [Chapter 7, Section 4](https://flylib.com/books/en/2.823.1.64/1/) of [Code Complete](https://en.wikipedia.org/wiki/Code_Complete),\nSteve McConnell lays out some evidence for and against longer functions.  The results are mixed, but many\nof the studies he cites show better errors-per-line metrics for *larger*, rather than smaller, functions.\n\nThere are [newer studies](https://arxiv.org/pdf/2205.01842#:~:text=In%20this%20paper%20we%20examine,also%20decreases%20overall%20maintenance%20efforts)\nas well that argue for smaller functions ( 200LOC, and a walk around the SQLite\ncodebase will furnish many other examples of large functions.  SQLite is noted for being extremely high quality and\nvery well maintained.\n\nOr consider the [`ChromeContentRendererClient::RenderFrameCreated()`](https://github.com/chromium/chromium/blob/6fdb8fdff0ba83db148ff2f87105bc95e5a4ceec/chrome/renderer/chrome_content_renderer_client.cc#L591)\nfunction in the [Google Chrome](https://www.google.com/chrome/index.html) Web Browser.  Also looks to be over 200 LOC.  Again, poking around the codebase\nwill give you plenty of other long functions to look at.  Chrome is solving one of the hardest problems in software:\nbeing a good general purpose hypermedia client.  And yet their code doesn’t look very “clean” to me.\n\nNext, consider the [`kvstoreScan()`](https://github.com/redis/redis/blob/3fcddfb61f903d7112da186cba8b1c93a99dc87f/src/kvstore.c#L359)\nfunction in [Redis](https://redis.io/).  Smaller, on the order of 40LOC, but still far larger than Clean Code would\nsuggest.  A quick scan through the Redis codebase will furnish many other “dirty” examples.\n\nThese are all C-based projects, so maybe the rule of small functions only applies to object-oriented languages, like\nJava?\n\nOK, take a look at the [`update()`](https://github.com/JetBrains/intellij-community/blob/8c6cc1579ac358451ba2c5b8a54853249fdc5451/java/compiler/impl/src/com/intellij/compiler/actions/CompileAction.java#L60)\nfunction in the `CompilerAction` class of [IntelliJ](https://www.jetbrains.com/idea/), which is roughly 90LOC.  Again,\npoking around their codebase will reveal many other large functions well over 50LOC.\n\nSQLite, Chrome, Redis & IntelliJ…\n\nThese are important, complicated, successful & well maintained pieces of software, and yet\nwe can find large functions in all of them.\n\nNow, I don’t want to imply that any of the engineers on these projects agree with this essay in any way, but I think\nthat we have some fairly good evidence that longer functions are OK in software projects.  It seems safe to say that\nbreaking up functions just to keep them small is not necessary.  Of course you can consider doing so for other reasons\nsuch as code reuse, but being small just for small’s sake seems unnecessary.\n\n## I Prefer Integration Tests to Unit Tests\n\nI am a huge fan of testing and highly recommend testing software as a key component of building maintainable systems.\n\nhtmx itself is only possible because we have a good [test suite](https://htmx.org/test) that helps us ensure\nthat the library stays stable as we work on it.\n\nIf you take a look at the [test suite](https://github.com/bigskysoftware/htmx/blob/master/test/core/ajax.js) one thing\nyou might notice is the relative lack of [Unit Tests](https://en.wikipedia.org/wiki/Unit_testing).  We have very few\ntest that directly call functions on the htmx object.  Instead, the tests are mostly\n[Integration Tests](https://en.wikipedia.org/wiki/Integration_testing): they set up a particular DOM configuration\nwith some htmx attributes and then, for example, click a button and verify some things about the state of the DOM afterward.\n\nThis is in contrast with Clean Code’s recommendation of extensive *unit testing*, coupled with Test-First Development:\n\n**First Law** You may not write production code until you have written a failing unit test.\n**Second Law** You may not write more of a unit test than is sufficient to fail, and not compiling is failing.\n**Third** Law You may not write more production code than is sufficient to pass the currently failing test.\n\n–[Clean Code](https://www.goodreads.com/book/show/3735293-clean-code)\n\nI generally avoid doing this sort of thing, especially early on in projects.  Early on you often have no\nidea what the right abstractions for your domain are, and you need to try a few different approaches to figure out what\nyou are doing.  If you adopt the test first approach you end up with a bunch of tests that are going to break as you\nexplore the problem space, trying to find the right abstractions.\n\nFurther, unit testing encourages the exhaustive testing of every single function you write, so you often end up having more\ntests that are tied to a particular implementation of things, rather than the high level API or conceptual ideas of the\nmodule of code.\n\nOf course, you can and should refactor your tests as you change things, but the reality is that a large and growing test\nsuite takes on its own mass and momentum in a project, especially as other engineers join, making changes more and more\ndifficult as they are added.  You end up creating things like test helpers, mocks, etc. for your testing code.\n\nAll that code and complexity tends over time to lock you in to a particular implementation.\n\n### Dirty Testing\n\nMy preferred approach in many projects is to do some unit testing, but not a ton, early on in the project and wait\nuntil the core APIs and concepts of a module have crystallized.\n\nAt that point I then test the API exhaustively with integrations tests.\n\nIn my experience, these integration tests are much more useful than unit tests, because they remain stable and useful\neven as you change the implementation around.  They aren’t as tied to the current codebase, but rather express higher level\ninvariants that survive refactors much more readily.\n\nI have also found that once you have a few higher-level integration tests, you can then do Test-Driven development, but\nat the higher level: you don’t think about units of code, but rather the API you want to achieve, write the tests for that\nAPI and then implement it however you see fit.\n\nSo, I think you should hold off on committing to a large test suite until later in the project, and that test suite\nshould be done at a higher level than Test-First Development suggests.\n\nGenerally, if I can write a higher-level integration test\nto demonstrate a bug or feature I will try to do so, with the hope that the higher-level test will have a longer shelf\nlife for the project.\n\n## I Prefer To Minimize Classes\n\nA final coding strategy that I use is that I generally strive to minimize the number of classes/interfaces/concepts in\nmy projects.\n\nClean Code does not explicitly say that you should maximize the # of classes in your system, but many recommendations it\nmakes tend to lead to this outcome:\n\n- “Prefer Polymorphism to If/Else or Switch/Case”\n\n- “The first rule of classes is that they should be small. The second rule of classes is that they should be smaller\nthan that.”\n\n- “The Single Responsibility Principle (SRP) states that a class or module should have one, and only one, reason to\nchange.”\n\n- “The first thing you might notice is that the program got a lot longer. It went from a little over one page to\nnearly three pages in length.”\n\nAs with functions, I don’t think classes should be particularly small, or that you should prefer polymorphism to a\nsimple (or even a long, janky) if/else statement, or that a given module or class should only have one reason to change.\n\nAnd I think the last sentence here is a good hint why: you tend to end up with a lot more code which may be of little\nreal benefit to the system.\n\n### “God” Objects\n\nYou will often hear people criticise the idea of [“God objects”](https://en.wikipedia.org/wiki/God_object) and I\ncan of course understand where this criticism comes from: an incoherent class or module with a morass of unrelated\nfunctions is obviously a bad thing.\n\nHowever, I think that fear of “God objects” can tend to lead to an opposite problem: overly-decomposed software.\n\nTo balance out this fear, let’s look at one of my favorite software packages,\n[Active Record](https://guides.rubyonrails.org/active_record_basics.html).\n\nActive Record provides a way for you to map ruby object to a database, it is what is called an\n[Object/Relational Mapping](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) tool.\n\nAnd it does a great job of that, in my opinion: it makes the easy stuff easy,\nthe medium stuff easy enough, and when push comes to shove you can kick out to raw SQL without much fuss.\n\n(This is a great example of what I call [“layering”](https://grugbrain.dev/#grug-on-apis) an API.)\n\nBut that’s not all the Active Record objects are good at: they also provide excellent functionality for building HTML\nin the [view layer](https://guides.rubyonrails.org/action_view_overview.html) of Rails.  They don’t include *HTML specific* functionality, but they do offer functionality\nthat is useful on the view side, such as providing an API to retrieve error messages, even at the field level.\n\nWhen you are writing Ruby on Rails applications you simply pass your Active Record instances out to the view/templates.\n\nCompare this with a more heavily factored implementation, where validation errors are handled as their own “concern”.\nNow you need to pass (or at least access) two different things in order to properly generate your HTML.  It’s not\nuncommon in the Java community to adopt the [DTO](https://www.baeldung.com/java-dto-pattern) pattern and have another set of objects entirely\ndistinct from the ORM layer that is passed out to the view.\n\nI like the Active Record approach.  It may not be [separating concerns](https://en.wikipedia.org/wiki/Separation_of_concerns)\nwhen looked at from a purist perspective, but *my* concern is often getting data from a database into an HTML document,\nand Active Record does that job admirably without me needing to deal with a bunch of other objects along the way.\n\nThis helps me minimize the total number of objects I need to deal with in the system.\n\nWill some functionality creep into a model that is maybe a bit “view” flavored?\n\nSure, but that’s not the end of the world, and it reduces the number\nof layers and concepts I have to deal with.  Having one class that handles retrieving data from the database, holding\ndomain logic and serves as a vessel for presenting information to the view layer simplifies things tremendously for me.\n\n## Conclusion\n\nI’ve given three examples of my codin’ dirty approach:\n\n- I think (some) big functions are good, actually\n\n- I prefer integration tests to unit tests\n\n- I like to keep my class/interface/concept count down\n\nI’m presenting this, again, not to convince *you* to code the way *I* code, or to suggest that the way I code is “optimal” in\nany way.\n\nRather it is to give you, and especially you younger developers out there, a sense that you don’t *have* to write code\nthe way that many thought leaders suggest in order to have a successful software career.\n\nYou shouldn’t be intimidated if someone calls your code “dirty”: lots of very successful software has been written that\nway and, if you focus on the [core ideas](https://en.wikipedia.org/wiki/Algorithms_%2B_Data_Structures_%3D_Programs)\nof [software engineering](https://www.web.stanford.edu/~ouster/cgi-bin/book.php), you will likely be successful\nin spite of how “dirty” it is, and maybe even because of it!","publishedAt":"2024-11-24T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.963Z","url":"https://htmx.org/essays/codin-dirty/","media":[],"coverageCount":0},{"id":"rel_-N8B3oLZamadNJI2gE2nY","version":null,"type":"feature","title":"Web Components Work Great with htmx","summary":"People interested in htmx often ask us about component libraries.\nReact and other JavaScript frameworks have great ecosystems of pre-built components ...","titleGenerated":null,"titleShort":null,"content":"People interested in htmx often ask us about component libraries.\nReact and other JavaScript frameworks have great ecosystems of pre-built components that can be imported into your project; htmx doesn’t really have anything similar.\n\nThe first and most important thing to understand is that htmx doesn’t preclude you from using *anything*.\nBecause htmx-based websites are [often multi-page apps](https://unplannedobsolescence.com/blog/less-htmx-is-more/), each page is a blank canvas on which you can import as much or as little JavaScript as you like.\nIf your app is largely hypermedia, but you want an interactive, React-based calendar for one page, just import it on that one page with a script tag.\n\nWe sometimes call this pattern “Islands of Interactivity”—it’s referenced in our explainers [here](https://htmx.org/essays/10-tips-for-ssr-hda-apps/#tip-8-when-necessary-create-islands-of-interactivity), [here](https://htmx.org/essays/hypermedia-friendly-scripting/#islands), and [here](https://htmx.org/essays/you-cant/#myth-5-with-htmx-or-mpas-every-user-action-must-happen-on-the-server).\nUnlike JS frameworks, which are largely incompatible with each other, using islands with htmx won’t lock you into any specific paradigm.\n\nBut there’s a second way that you can re-use complex frontend functionality with htmx, and it’s [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)!\n\n## Practical Example\n\nLet’s say that you have a table that says what carnival rides everyone is signed up for:\n\n  \n    Name\n    Carousel\n    Roller Coaster\n  \n  \n    Alex\n    Yes\n    No\n  \n  \n    Sophia\n    Yes\n    Yes\n  \n\nAlex is willing to go on the carousel but not the roller coaster, because he is scared; Sophia is not scared of either.\n\nI built this as a regular HTML table ([closing tags are omitted](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td#technical_summary) for clarity):\n\n```\ntable>\n  tr>th>Name    th>Carousel  th>Roller Coaster\n  tr>td>Alex    td>Yes       td>No\n  tr>td>Sophia  td>Yes       td>Yes\ntable>\n\n```\n\nNow imagine we want to make those rows editable.\nThis is a classic situation in which people reach for frameworks, but can we do it with hypermedia?\nSure!\nHere’s a naive idea:\n\n```\nform hx-put=/carnival>\ntable>\n  tr>\n    th>Name\n    th>Carousel\n    th>Roller Coaster\n  tr>\n  tr>\n    td>Alex\n    td>select name=\"alex-carousel\"> option selected>Yes option>No option> Maybeselect>\n    td>select name=\"alex-roller\"> option>Yes option selected>No option> Maybeselect>\n  tr>\n  tr>\n    td>Sophia\n    td>select name=\"sophia-carousel\"> option selected>Yes option>No option> Maybeselect>\n    td>select name=\"sophia-roller\"> option selected>Yes option>No option> Maybeselect>\n  tr>\ntable>\nbutton>Savebutton>\nform>\n\n```\n\nThat will give us this table:\n\n  \n    Name\n    Carousel\n    Roller Coaster\n  \n  \n    Alex\n    \n    \n  \n  \n    Sophia\n    \n    \n  \n\nSave\n\nThat’s not too bad!\nThe save button will submit all the data in the table, and the server will respond with a new table that reflects the updated state.\nWe can also use CSS to make the ``s fit our design language.\nBut it’s easy to see how this could start to get unwieldy—with more columns, more rows, and more options in each cell, sending all that information each time starts to get costly.\n\nLet’s remove all that redundancy with a web component!\n\n```\nform hx-put=/carnival>\ntable>\n  tr>\n    th>Name\n    th>Carousel\n    th>Roller Coaster\n  tr>\n  tr>\n    td>Alex\n    td>edit-cell name=\"alex-carousel\" value=\"Yes\">edit-cell>\n    td>edit-cell name=\"alex-roller\" value=\"No\">edit-cell>\n  tr>\n  tr>\n    td>Sophia\n    td>edit-cell name=\"sophia-carousel\" value=\"Yes\">edit-cell>\n    td>edit-cell name=\"sophia-roller\" value=\"Yes\">edit-cell>\n  tr>\ntable>\nbutton>Savebutton>\nform>\n\n```\n\nWe still have an entirely declarative [HATEOAS](https://htmx.org/essays/hateoas/) interface—both current state (the `value` attribute) and possible actions on that state (the `` and `` elements) are efficiently encoded in the hypertext—only now we’ve expressed the same ideas a lot more concisely.\nhtmx can add or remove rows (or better yet, whole tables) with the `` web component as if `` were a built-in HTML element.\n\nYou’ve probably noticed that I didn’t include the implementation details for `` (although you can, [of course](https://htmx.org/essays/right-click-view-source/), View Source this page to see them).\nThat’s because they don’t matter!\nWhether the web component was written by you, or a teammate, or a library author, it can be used exactly like a built-in HTML element and htmx will handle it just fine.\n\n## Don’t Web Components have some problems?\n\nA lot of the problems that JavaScript frameworks have supporting Web Components don’t apply to htmx.\n\nWeb Components [have DOM-based lifecycles](https://dev.to/ryansolid/web-components-are-not-the-future-48bh), so they are difficult for JavaScript frameworks, which often manipulate elements outside of the DOM, to work with.\nFrameworks have to account for some [bizarre and arguably buggy](https://x.com/Rich_Harris/status/1841467510194843982) APIs that behave differently for native DOM elements than they do for custom ones.\nHere at htmx, we agree with [SvelteJS creator Rich Harris](https://x.com/Rich_Harris/status/1839484645194277111): “web components are [not] useful primitives on which to build web frameworks.”\n\nThe good news is that htmx [is not really a JavaScript web framework](https://htmx.org/essays/is-htmx-another-javascript-framework/).\nThe DOM-based lifecycles of custom elements work great in htmx, because everything in htmx has a DOM-based lifecycle—we get stuff from the server, and we add it to the DOM.\nThe default htmx swap style is to just set [`.innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML), and that works great for the vast majority of users.\n\nThat’s not to say that htmx doesn’t have to accommodate weird Web Component edge cases.\nOur community member and resident WC expert [Katrina Scialdone](https://unmodernweb.com/) merged [Shadow DOM support for htmx 2.0](https://github.com/bigskysoftware/htmx/pull/2075), which lets htmx process the implementation details of a Web Component,\nand supporting that is [occasionally](https://github.com/bigskysoftware/htmx/pull/2846) [frustrating](https://github.com/bigskysoftware/htmx/pull/2866).\nBut being able to work with both the [Shadow DOM](https://htmx.org/examples/web-components/) and the [“Light DOM”](https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/) is a nice feature for htmx, and it carries a relatively minimal support burden because htmx just isn’t doing all that much.\n\n## Bringing Behavior Back to the HTML\n\nA couple of years ago, W3C Contributor (and Web Component proponent, I think) Lea Verou wrote the following, in a blog post about [“The failed promise of Web Components”](https://lea.verou.me/blog/2020/09/the-failed-promise-of-web-components/):\n\nthe main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but expect JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS.\n\nLea is identifying an issue that, from the perspective of 2020, would have seemed impossible to solve: the cutting-edge web developers targeted by Web Components were not writing HTML, they were writing JSX, usually with React (or Vue, or what have you).\nThe idea that [behavior belongs in the HTML](https://unplannedobsolescence.com/blog/behavior-belongs-in-html/) was, in the zeitgeist, considered [a violation of separation of concerns](https://htmx.org/essays/locality-of-behaviour/);\ndisrespecting HTML was best practice.\n\nThe relatively recent success of htmx—itself now a participant in the zeitgeist—offers an alternative path: take HTML seriously again.\nIf your website is one whose functionality can be primarily described with [large-grain hypermedia transfers](https://htmx.org/essays/when-to-use-hypermedia/) (we believe most of them can), then the value of being able to express more complex patterns through hypermedia increases dramatically.\nAs more developers use htmx (and multi-page architectures generally) to structure their websites,\nperhaps the demand for Web Components will increase along with it.\n\nDo Web Components “just work” everywhere? Maybe, maybe not. But they do work here.\n\nclass EditCell extends HTMLElement {\n  connectedCallback() {\n    this.value = this.getAttribute(\"value\")\n    this.name = this.getAttribute(\"name\")\n\n    this.innerHTML = `\n      \n        Yes\n        No\n        Maybe\n      \n    `\n  }\n}\n\ncustomElements.define('edit-cell', EditCell)","publishedAt":"2024-11-13T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.963Z","url":"https://htmx.org/essays/webcomponents-work-great/","media":[],"coverageCount":0},{"id":"rel_GhhUU84YHI1VgqVg3pad0","version":null,"type":"feature","title":"Next.js to htmx — A Real World Example","summary":"Over 6 years ago, I created [an open source URL shortener](https://github.com/thedevs-network/kutt) with Next.js and after years of working on it, I f...","titleGenerated":null,"titleShort":null,"content":"Over 6 years ago, I created [an open source URL shortener](https://github.com/thedevs-network/kutt) with Next.js and after years of working on it, I found Next.js to be much more of a burden than a help. Over the years, Next.js has changed, and so did my code so it can be compatible with those changes.\n\nMy Next.js codebase grew bigger, and its complexity increased by greater size. I had dozens of components and a list of dependencies to manage. I ended up maintaining the code constantly just to keep it alive. Sure, Next.js helped here and there, but at what cost?\n\nI asked myself, what am I doing on my website that is so complex that needs all that JavaScript code to decide what to render and how to render on my webpage? Next.js was trying to render the webpage from the server side, so why won’t I send the HTML directly myself?\n\nSo I decided to try a new route—some might say the good ol’ route—and choose plain HTML and use the help of htmx for that.\n\n## Video\n\nWatch me go full in details here:\n\n[Video](https://www.youtube.com/watch?v=8RL4NvYZDT4)\n\n## The process\n\nReplacing my components with the equivalent HTML elements powered by htmx wasn’t exactly an easy task, but one that was worth the time. I had to view things from a different angle, and I sometimes felt strict in what user interactions I can implement, but what I created was reliable and fast.\n\nAll the build steps were gone; no more transpiling and compiling the code. What you see is what you get. Most of the dependencies became redundant and have been removed. All the main logic of the website was moved to the server side, holding one source for the truth.\n\nIn the Next.js version I had isolated components, global states, and all that JavaScript to handle the forms or update the content, and yet, everything was more intuitive with htmx. After trying it, sending and receiving HTML suddenly made sense.\n\n## Summary\n\n- Dependencies are **reduced by 87%** (**24** to **3**!)\n\n- I wrote **less code by 17%**  (**9500 LOC** to **7900 LOC**.) In reality the total LOC of the code base is **reduced by more than 50%**, since much less code is imported from the dependencies.\n\n- Web build time was **reduced by 100%** (there’s **no build step** anymore.)\n\n- Size of the website **reduced by more than 85%** (**~800KB** to **~100KB**!)\n\nThese numbers signify a great improvement, however, what is important for me at the end is the user and the developer experience, which to me htmx won at both.","publishedAt":"2024-11-07T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.963Z","url":"https://htmx.org/essays/a-real-world-nextjs-to-htmx-port/","media":[{"type":"video","url":"https://www.youtube.com/watch?v=8RL4NvYZDT4"},{"type":"video","url":"https://i.ytimg.com/vi/8RL4NvYZDT4/hqdefault.jpg","alt":"Next.js to htmx – A Real World Example","linkUrl":"https://www.youtube.com/watch?v=8RL4NvYZDT4","r2Key":"releases/5f7aa1d8a592bee772161802462ea6209b3052649d95cdd147d79e178a62ca78.jpg","r2Url":"https://media.releases.sh/releases/5f7aa1d8a592bee772161802462ea6209b3052649d95cdd147d79e178a62ca78.jpg"}],"coverageCount":0},{"id":"rel_kH-X4vNI2a8mrNs2sy36R","version":null,"type":"feature","title":"Why Gumroad Didn't Choose htmx","summary":"At Gumroad, we recently embarked on a new project called [Helper](https://helper.ai). As the CEO, I was initially quite\noptimistic about using [htmx](...","titleGenerated":null,"titleShort":null,"content":"At Gumroad, we recently embarked on a new project called [Helper](https://helper.ai). As the CEO, I was initially quite\noptimistic about using [htmx](https://htmx.org) for this project, even though some team members were less enthusiastic.\n\nMy optimism stemmed from previous experiences with React, which often felt like overkill for our needs. I thought htmx\ncould be a good solution to keep our front-end super light.\n\n[\n![GitHub screenshot shows deleted files](/img/gumroad-red.jpeg)\n](/img/gumroad-red.jpeg) \nSource with htmx - Click Image To View\n\nIn fact, I shared this sentiment with our team in Slack:\n\n“https://htmx.org/ may be a way of adding simple interactions to start”\n\nAnd initially, it seemed promising! As one of our engineers at Gumroad eloquently put it:\n\n“HTMX is (officially) a meme to make fun of how overly complicated the JS landscape has gotten - much like tailwind is\njust a different syntax for inline CSS, HTMX is a different syntax for inline JS.”\n\nHowever, unlike Tailwind, which has found its place in our toolkit, htmx didn’t scale for our purposes and didn’t lead\nto the best user experience for our customers–at least for our use case.\n\nHere’s why:\n\n- \n\n**Intuition and Developer Experience**: While it would have been possible to do the right thing in htmx, we found it\nmuch more intuitive and fun to get everything working with Next.js. The development process felt natural with\nNext.js, whereas with htmx, it often felt unnatural and forced. For example, when building complex forms with dynamic\nvalidation and conditional fields, we found ourselves writing convoluted server-side logic to handle what would be\nstraightforward client-side operations in React.\n\n- \n\n**UX Limitations**: htmx ended up pushing our app towards a Rails/CRUD approach, which led to a really poor (or at\nleast, boring and generic) user experience by default. We found ourselves constantly fighting against this tendency,\nwhich was counterproductive. For instance, implementing a drag-and-drop interface for our workflow builder proved to\nbe a significant challenge with htmx, requiring workarounds that felt clunky compared to the smooth experience we\ncould achieve with React libraries.\n\n- \n\n**AI and Tooling Support**: It’s worth noting that AI tools are intimately familiar with Next.js and not so much with\nhtmx, due to the lack of open-source training data. This is similar to the issue Rails faces. While not a\ndealbreaker, it did impact our development speed and the ease of finding solutions to problems. When we encountered\nissues, the wealth of resources available for React/Next.js made troubleshooting much faster.\n\n- \n\n**Scalability Concerns**: As our project grew in complexity, we found htmx struggling to keep up with our needs. The\nsimplicity that initially attracted us began to feel limiting as we tried to implement more sophisticated\ninteractions and state management. For example, as we added features like real-time collaboration and complex data\nvisualization, managing state across multiple components became increasingly difficult with htmx’s server-centric\napproach.\n\n- \n\n**Community and Ecosystem**: The React/Next.js ecosystem is vast and mature, offering solutions to almost any problem\nwe encountered. With htmx, we often found ourselves reinventing the wheel or compromising on functionality. This\nbecame particularly evident when we needed to integrate third-party services and libraries, which often had React\nbindings but no htmx equivalents.\n\n[\n![GitHub: 1 added file](/img/gumroad-green.jpeg)\n](/img/gumroad-green.jpeg) \nSource with Next.js - Click Image To View\n\nUltimately, we ended up moving to React/Next.js, which has been a really great fit for building the complex UX we’ve\nbeen looking for. We’re happy with this decision–for now. It’s allowed us to move faster, create more engaging user\nexperiences, and leverage a wealth of existing tools and libraries.\n\n[\n![The old Gumroad Helper interface is quite basic with a single flat form,\n  while the new one has multiple levels of navigation and editable lists.](/img/gumroad-helper-before-after.png)\n](/img/gumroad-helper-before-after.png) \nGumroad Helper Before & After - Click Image To View\n\nThis experience has reinforced a valuable lesson: while it’s important to consider lightweight alternatives, it’s\nequally crucial to choose technologies that can grow with your project and support your long-term vision. For Helper,\nReact and Next.js have proven to be that choice.\n\nSince we’ve moved there, we’ve been able to seriously upgrade our app’s user experience for our core customers.\n\n- \n\n**Drag-and-Drop Functionality**: One of the key features of our workflow builder is the ability to reorder steps\nthrough drag-and-drop. While it’s possible to implement drag-and-drop with htmx, we found that the available\nsolutions felt clunky and required significant custom JavaScript. In contrast, React ecosystem offers libraries like\nreact-beautiful-dnd that provide smooth, accessible drag-and-drop with minimal setup.\n\n- \n\n**Complex State Management**: Each workflow step has its own set of configurations and conditional logic. As users\nedit these, we need to update the UI in real-time to reflect changes and their implications on other steps. With\nhtmx, this would require numerous server roundtrips or complex client-side state management that goes against htmx’s\nserver-centric philosophy. React’s state management solutions (like useState or more advanced options like Redux)\nmade this much more straightforward.\n\n- \n\n**Dynamic Form Generation**: The configuration for each step type is different and can change based on user input.\nGenerating these dynamic forms and handling their state was more intuitive with React’s component model. With htmx,\nwe found ourselves writing more complex server-side logic to generate and validate these forms.\n\n- \n\n**Real-time Collaboration**: While not visible in this screenshot, we implemented features allowing multiple users to\nedit a workflow simultaneously. Implementing this with WebSockets and React was relatively straightforward, whereas\nwith htmx, it would have required more complex server-side logic and custom JavaScript to handle real-time updates.\n\n- \n\n**Performance Optimization**: As workflows grew larger and more complex, we needed fine-grained control over\nrendering optimizations. React’s virtual DOM and hooks like useMemo and useCallback allowed us to optimize\nperformance in ways that weren’t as readily available or intuitive with htmx.\n\nIt’s important to note that while these challenges aren’t insurmountable with htmx, we found that addressing them often\nled us away from htmx’s strengths and towards solutions that felt more natural in a JavaScript-heavy environment. This\nrealization was a key factor in our decision to switch to React and Next.js.\n\nWe acknowledge that htmx may be a great fit for many projects, especially those with simpler interaction models or those\nbuilt on top of existing server-rendered applications. Our experience doesn’t invalidate the benefits others have found\nin htmx. The key is understanding your project’s specific needs and choosing the tool that best aligns with those\nrequirements.\n\nIn our case, the complex, stateful nature of Helper’s interface made React and Next.js a better fit. However, we\ncontinue to appreciate htmx’s approach and may consider it for future projects where its strengths align better with our\nneeds.\n\nThat said, we’re always open to reevaluating our tech stack as our needs evolve and new technologies emerge. Who knows\nwhat the future might bring?","publishedAt":"2024-09-30T00:00:00.000Z","fetchedAt":"2026-05-05T13:23:57.963Z","url":"https://htmx.org/essays/why-gumroad-didnt-choose-htmx/","media":[],"coverageCount":0}],"pagination":{"nextCursor":"2024-09-30T00:00:00.000Z|2026-05-05T13:23:57.963Z|rel_kH-X4vNI2a8mrNs2sy36R","limit":20},"summaries":{"rolling":null,"monthly":[]}}