<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Michael Mikus</title><description>Writing on embedded systems, firmware, and developer experience.</description><link>https://mikus.io/</link><item><title>The books r/embedded keeps recommending</title><link>https://mikus.io/blog/books-embedded-engineers-recommend/</link><guid isPermaLink="true">https://mikus.io/blog/books-embedded-engineers-recommend/</guid><description>I counted the books named in 562 r/embedded threads about reading, from 2011 to October 2026. One title leads by a wide margin, more than half the mentions go to books that are not about embedded, and some of the most recommended resources are not books.</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“What book should I read?” is one of the most common questions on &lt;a href=&quot;https://www.reddit.com/r/embedded/&quot;&gt;r/embedded&lt;/a&gt;. It gets asked by students, by software developers moving closer to the hardware, and by electrical engineers who want to write better firmware. The answers are scattered across hundreds of threads, so I collected them and counted.&lt;/p&gt;
&lt;p&gt;The short version: one book is named far more often than any other, the rest of the list is surprisingly wide, and the community’s taste has been shifting toward hardware.&lt;/p&gt;
&lt;h2 id=&quot;how-i-counted&quot;&gt;How I counted&lt;/h2&gt;
&lt;p&gt;Reddit’s own site refuses automated requests like mine, so I used the &lt;a href=&quot;https://arctic-shift.photon-reddit.com/&quot;&gt;Arctic Shift&lt;/a&gt; archive of public Reddit posts and comments. From it I took every r/embedded post that mentions books and kept the threads with at least three comments where either the title talks about books or the title asks for learning resources and the post itself mentions books.&lt;/p&gt;
&lt;p&gt;That left &lt;strong&gt;562 threads&lt;/strong&gt; from August 2011 to 2 October 2026, with &lt;strong&gt;7,502 comments by 3,233 different people&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I then matched the comments against a list of 92 titles, built from the books people actually link to and name in these threads. For each title I matched the author’s name and the distinctive part of the title. Titles that also read as ordinary phrases, like “Clean Code” or “Programming Embedded Systems”, only counted when written as a title or in a comment that also says “book”. I checked samples of the matches by hand and tightened the patterns where they caught the wrong thing, such as “Effective C” matching “Effective C++”, or mentions of an author’s podcast or video course instead of their book.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;mention&lt;/strong&gt; below means one person naming a book in one thread. Someone who repeats a title three times in the same thread counts once.&lt;/p&gt;
&lt;h2 id=&quot;the-ranking&quot;&gt;The ranking&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Book&lt;/th&gt;
&lt;th&gt;Author&lt;/th&gt;
&lt;th&gt;Mentions&lt;/th&gt;
&lt;th&gt;Threads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.oreilly.com/library/view/making-embedded-systems/9781098151539/&quot;&gt;Making Embedded Systems&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Elecia White&lt;/td&gt;
&lt;td&gt;105&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://artofelectronics.net/&quot;&gt;The Art of Electronics&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Paul Horowitz, Winfield Hill&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.informit.com/store/c-programming-language-9780131103627&quot;&gt;The C Programming Language&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Brian Kernighan, Dennis Ritchie&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://leanpub.com/mastering-stm32-2nd&quot;&gt;Mastering STM32&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Carmine Noviello&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/&quot;&gt;Test-Driven Development for Embedded C&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;James Grenning&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.mheducation.com/highered/mhp/product/practical-electronics-inventors-fourth-edition.html&quot;&gt;Practical Electronics for Inventors&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Paul Scherz, Simon Monk&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.makershed.com/products/make-avr-programming&quot;&gt;Make: AVR Programming&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Elliot Williams&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://link.springer.com/book/9783662742990&quot;&gt;Real-Time C++&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Christopher Kormanyos&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.packtpub.com/en-us/product/mastering-embedded-linux-development-9781803232591&quot;&gt;Mastering Embedded Linux Programming&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Frank Vasquez, Chris Simmonds&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://lwn.net/Kernel/LDD3/&quot;&gt;Linux Device Drivers, 3rd edition&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Corbet, Rubini, Kroah-Hartman&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.informit.com/store/clean-code-a-handbook-of-agile-software-craftsmanship-9780135398579&quot;&gt;Clean Code&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Robert C. Martin&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://link.springer.com/book/10.1007/978-1-4842-8279-3&quot;&gt;Embedded Software Design&lt;/a&gt;, &lt;a href=&quot;https://link.springer.com/book/10.1007/978-1-4842-3297-2&quot;&gt;Reusable Firmware Development&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Jacob Beningo&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://shop.elsevier.com/books/the-definitive-guide-to-arm-cortex-m3-and-cortex-m4-processors/yiu/978-0-12-408082-9&quot;&gt;The Definitive Guide to ARM Cortex-M3 and Cortex-M4&lt;/a&gt;, and others&lt;/td&gt;
&lt;td&gt;Joseph Yiu&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.routledge.com/MicroCOS-II-The-Real-Time-Kernel/Labrosse/p/book/9781578201037&quot;&gt;MicroC/OS-II&lt;/a&gt; and MicroC/OS-III&lt;/td&gt;
&lt;td&gt;Jean Labrosse&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://users.ece.utexas.edu/~valvano/&quot;&gt;Embedded Systems textbooks and free e-book&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Jonathan Valvano&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://shop.elsevier.com/books/the-art-of-designing-embedded-systems/ganssle/978-0-7506-8644-0&quot;&gt;The Art of Designing Embedded Systems&lt;/a&gt;, and others&lt;/td&gt;
&lt;td&gt;Jack Ganssle&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each title links to the publisher’s page for the latest edition. Where the authors keep an official site for the book or there is an official free copy, as with &lt;em&gt;The Art of Electronics&lt;/em&gt;, &lt;em&gt;Linux Device Drivers&lt;/em&gt; and Valvano’s books, the link goes there instead. The fourth edition of &lt;em&gt;Mastering Embedded Linux Programming&lt;/em&gt; was renamed &lt;em&gt;Mastering Embedded Linux Development&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Below these, mentions thin out quickly. &lt;a href=&quot;https://www.informit.com/store/effective-c-plus-plus-55-specific-ways-to-improve-your-programs-9780321334879&quot;&gt;Effective C++&lt;/a&gt;, &lt;a href=&quot;https://www.informit.com/store/embedded-software-primer-9780201615692&quot;&gt;An Embedded Software Primer&lt;/a&gt;, &lt;a href=&quot;https://www.manning.com/books/modern-c-third-edition&quot;&gt;Modern C&lt;/a&gt;, &lt;a href=&quot;https://www.oreilly.com/library/view/programming-embedded-systems/0596009836/&quot;&gt;Programming Embedded Systems&lt;/a&gt; and &lt;a href=&quot;https://www.packtpub.com/en-us/product/hands-on-rtos-with-microcontrollers-9781803237725&quot;&gt;Hands-On RTOS with Microcontrollers&lt;/a&gt; each have between 11 and 14. Of the 92 titles on my list, 83 were named at least once, and the top ten account for almost half of all mentions.&lt;/p&gt;
&lt;p&gt;Only 291 of the 562 threads name a book from the list at all. The others are answered with courses, boards, projects and general advice, or drift to other topics.&lt;/p&gt;
&lt;h2 id=&quot;one-book-stands-out&quot;&gt;One book stands out&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Making Embedded Systems&lt;/em&gt; is named in 78 threads, which is more than one in four of the threads that name any book at all. It leads in every period I looked at, with 105 mentions against 61 for second place.&lt;/p&gt;
&lt;p&gt;The comments explain why. It is described as a broad, readable introduction that covers the whole spectrum of building an embedded system, rather than one chip or one technique. One commenter called it “the gold standard”. The main reservation is the mirror image of the praise: it is a survey, not the most in-depth book on any single topic. The second edition, published by O’Reilly in 2024, added chapters on IoT and networked sensors, debugging, and data handling.&lt;/p&gt;
&lt;p&gt;Its author also co-hosts the &lt;a href=&quot;https://embedded.fm/&quot;&gt;Embedded.fm&lt;/a&gt; podcast, which is itself one of the most recommended resources in these threads (more on that below). Some of the book’s reach probably comes from that.&lt;/p&gt;
&lt;h2 id=&quot;more-than-half-is-not-about-embedded&quot;&gt;More than half is not about embedded&lt;/h2&gt;
&lt;p&gt;If you sort every mention into a category, only 47% go to books about embedded systems. The rest:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Share of mentions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Embedded systems and firmware&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C and C++&lt;/td&gt;
&lt;td&gt;17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electronics and hardware&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded Linux&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Computer architecture and theory&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General software craft&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time operating systems&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Shares are rounded. A handful of mentions of general-interest books, such as &lt;em&gt;The Soul of a New Machine&lt;/em&gt;, are left out.&lt;/p&gt;
&lt;p&gt;The second most named book is &lt;em&gt;The Art of Electronics&lt;/em&gt;, a dense electronics reference. The third is K&amp;amp;R, a C book whose current edition dates from 1988. &lt;em&gt;Clean Code&lt;/em&gt; ties for tenth. Embedded work sits between electrical engineering and software, and the reading list reflects that: it has circuits for people coming from software, and C and software design for people coming from hardware.&lt;/p&gt;
&lt;p&gt;The threads are also clear that the two electronics books are not interchangeable. &lt;em&gt;The Art of Electronics&lt;/em&gt; is praised as the reference people keep on the desk for years, and a few commenters warn that it is dense and not a beginner’s book. &lt;em&gt;Practical Electronics for Inventors&lt;/em&gt; is the one recommended as the practical way in.&lt;/p&gt;
&lt;h2 id=&quot;the-classics-are-old-and-the-threads-know-it&quot;&gt;The classics are old, and the threads know it&lt;/h2&gt;
&lt;p&gt;Several of the most named books are well over a decade old. K&amp;amp;R’s second edition dates from 1988, &lt;em&gt;Linux Device Drivers&lt;/em&gt; from 2005, and &lt;em&gt;Test-Driven Development for Embedded C&lt;/em&gt; from 2011. Commenters recommend them for fundamentals that have not changed much, and some are free: &lt;em&gt;Linux Device Drivers&lt;/em&gt; is &lt;a href=&quot;https://lwn.net/Kernel/LDD3/&quot;&gt;readable online at LWN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Commenters are open about the gaps. Some warn that K&amp;amp;R describes a version of C more than thirty years old, and in about a third of the threads where K&amp;amp;R comes up, someone also suggests a newer C book. &lt;a href=&quot;https://nostarch.com/effective-c-2nd-edition&quot;&gt;&lt;em&gt;Effective C&lt;/em&gt;&lt;/a&gt; by Robert Seacord (second edition 2024) and &lt;a href=&quot;https://www.manning.com/books/modern-c-third-edition&quot;&gt;&lt;em&gt;Modern C&lt;/em&gt;&lt;/a&gt; by Jens Gustedt (third edition 2025) both cover C23, and the &lt;a href=&quot;https://gustedt.gitlabpages.inria.fr/modern-c/&quot;&gt;manuscript of &lt;em&gt;Modern C&lt;/em&gt;&lt;/a&gt; is free to read under a Creative Commons licence for non-commercial use.&lt;/p&gt;
&lt;h2 id=&quot;learn-on-a-real-chip&quot;&gt;Learn on a real chip&lt;/h2&gt;
&lt;p&gt;Two of the top seven books are written for one family of microcontrollers. &lt;em&gt;Mastering STM32&lt;/em&gt; teaches ST’s STM32 parts through the official STM32Cube tools and runs to about 900 pages in its second edition. &lt;em&gt;Make: AVR Programming&lt;/em&gt; teaches the 8-bit AVR chips found on many Arduino boards, without the Arduino libraries.&lt;/p&gt;
&lt;p&gt;The community clearly likes learning on concrete hardware. But the two books are heading in different directions: &lt;em&gt;Make: AVR Programming&lt;/em&gt; appeared in 15% of the threads that named a book before 2020 and in 6% since 2023, while &lt;em&gt;Mastering STM32&lt;/em&gt; has held steady between 12% and 15%. That fits the wider move from 8-bit parts to 32-bit Arm Cortex-M. Joseph Yiu’s &lt;em&gt;Definitive Guide&lt;/em&gt; books on the Cortex-M cores, which explain the processor itself, are gaining a little.&lt;/p&gt;
&lt;h2 id=&quot;what-is-rising-and-what-is-fading&quot;&gt;What is rising and what is fading&lt;/h2&gt;
&lt;p&gt;I split the threads that name at least one book into three periods and compared how often each book appears in them. The earliest period has only 48 such threads, so treat small differences as noise. A few changes are large enough to mean something:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Book&lt;/th&gt;
&lt;th&gt;2011 to 2019&lt;/th&gt;
&lt;th&gt;2020 to 2022&lt;/th&gt;
&lt;th&gt;2023 to 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Making Embedded Systems&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;td&gt;21%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Art of Electronics&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Practical Electronics for Inventors&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hands-On RTOS with Microcontrollers&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test-Driven Development for Embedded C&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make: AVR Programming&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective C++ (Scott Meyers)&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jack Ganssle’s books&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Three trends stand out. Electronics books are gaining, and so are questions about hardware: threads with electronics or hardware in the title went from 2% of all threads before 2020 to 10% since 2023. Newer books are gaining ground: Brian Amos’s &lt;em&gt;Hands-On RTOS with Microcontrollers&lt;/em&gt; (2020, and a second edition with two co-authors in 2025) for RTOS work, and Jacob Beningo’s books, most recently &lt;em&gt;Embedded Software Design&lt;/em&gt; (2022), for architecture, while Jack Ganssle’s older books fade. And Scott Meyers’s &lt;em&gt;Effective C++&lt;/em&gt;, once a regular in C++ answers, has almost disappeared.&lt;/p&gt;
&lt;h2 id=&quot;some-of-the-best-answers-are-not-books&quot;&gt;Some of the best answers are not books&lt;/h2&gt;
&lt;p&gt;The same threads point to courses, podcasts and video channels as often as to many of the books on the list. Counted the same way:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Mentions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Miro Samek’s Modern Embedded Systems Programming (video)&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded.fm podcast&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FastBit Embedded Brain Academy courses&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shape the World course on edX (Valvano, Yerraballi)&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded Artistry blog and reading list&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phil’s Lab (video)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ben Eater (video)&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Any of the top four would rank sixth or seventh among the books. Jonathan Valvano is the clearest example: his free course is mentioned almost twice as often as his textbooks.&lt;/p&gt;
&lt;h2 id=&quot;if-you-only-read-three&quot;&gt;If you only read three&lt;/h2&gt;
&lt;p&gt;This section is my reading of the threads rather than a count. Taken together, the advice converges on a small set rather than one perfect book:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;One book about the whole job.&lt;/strong&gt; &lt;em&gt;Making Embedded Systems&lt;/em&gt; is the community’s default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One book about the language.&lt;/strong&gt; K&amp;amp;R is still the classic. &lt;em&gt;Effective C&lt;/em&gt; or &lt;em&gt;Modern C&lt;/em&gt; teach the C you will actually write today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One book about the hardware.&lt;/strong&gt; &lt;em&gt;Practical Electronics for Inventors&lt;/em&gt; to start, &lt;em&gt;The Art of Electronics&lt;/em&gt; as the reference you grow into.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then add a book written for the chip you are using, like &lt;em&gt;Mastering STM32&lt;/em&gt;, and a book about the area you work in, such as testing, embedded Linux or an RTOS.&lt;/p&gt;
&lt;h2 id=&quot;what-this-data-cant-tell-you&quot;&gt;What this data can’t tell you&lt;/h2&gt;
&lt;p&gt;A mention is not a review. Most of the mentions I read were recommendations, but some are warnings, like the “not for beginners” notes on &lt;em&gt;The Art of Electronics&lt;/em&gt;. The people asking and answering on Reddit also skew toward students and early-career engineers, which favours introductory books over specialist ones.&lt;/p&gt;
&lt;p&gt;The counts depend on my list of titles, so a book nobody names in a recognisable way is missing. The archive also thins out before 2018. And these numbers measure how often a book is recommended, not how good it is. They tell you what this community reaches for first, which is a useful place to start, but no more than that.&lt;/p&gt;
&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/embedded/&quot;&gt;r/embedded&lt;/a&gt;, posts and comments from August 2011 to 2 October 2026, retrieved through the &lt;a href=&quot;https://arctic-shift.photon-reddit.com/&quot;&gt;Arctic Shift&lt;/a&gt; archive&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oreilly.com/library/view/making-embedded-systems/9781098151539/&quot;&gt;Making Embedded Systems, second edition&lt;/a&gt;, O’Reilly&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lwn.net/Kernel/LDD3/&quot;&gt;Linux Device Drivers, third edition&lt;/a&gt;, free online at LWN&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.manning.com/books/modern-c-third-edition&quot;&gt;Modern C, third edition&lt;/a&gt;, Manning&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nostarch.com/effective-c-2nd-edition&quot;&gt;Effective C, second edition&lt;/a&gt;, No Starch Press&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leanpub.com/mastering-stm32-2nd&quot;&gt;Mastering STM32, second edition&lt;/a&gt;, Leanpub&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://users.ece.utexas.edu/~valvano/Volume1/E-Book/&quot;&gt;Embedded Systems: Shape the World&lt;/a&gt;, Jonathan Valvano and Ramesh Yerraballi&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Engineering</category></item><item><title>What to expect in Zephyr 4.5</title><link>https://mikus.io/blog/zephyr-4-5-what-to-expect/</link><guid isPermaLink="true">https://mikus.io/blog/zephyr-4-5-what-to-expect/</guid><description>Zephyr 4.5 is due in the last week of October 2026, and it is the last release before the next LTS. What is new, what will need changes in your project, and how to get ready.</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Zephyr 4.5 is scheduled for the week of 26 October 2026. Feature freeze is due in the week of 28 September, so the feature set is close to final. Everything below comes from the working drafts of the &lt;a href=&quot;https://docs.zephyrproject.org/latest/releases/release-notes-4.5.html&quot;&gt;release notes&lt;/a&gt; and &lt;a href=&quot;https://docs.zephyrproject.org/latest/releases/migration-guide-4.5.html&quot;&gt;migration guide&lt;/a&gt; in the Zephyr repository, as of 30 September. Details can still change before the release.&lt;/p&gt;
&lt;h2 id=&quot;when-it-lands-and-why-this-one-matters&quot;&gt;When it lands, and why this one matters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;th&gt;Week of&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature freeze (RC1)&lt;/td&gt;
&lt;td&gt;28 September 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second release candidate (RC2)&lt;/td&gt;
&lt;td&gt;12 October 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard freeze (RC3)&lt;/td&gt;
&lt;td&gt;19 October 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release&lt;/td&gt;
&lt;td&gt;26 October 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The dates come from the project’s &lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/wiki/Release-Management&quot;&gt;release schedule&lt;/a&gt; and are tentative.&lt;/p&gt;
&lt;p&gt;Zephyr now ships on a fixed six-month cadence, in April and October. That makes 4.5 the last release before LTS4, planned as Zephyr 4.6 in April 2027. If you plan to move to the LTS, testing 4.5 now spreads the migration work over two releases instead of meeting all of it at once in April. It also matters if you are still on 4.3: its support ends on 15 October 2026, before 4.5 ships.&lt;/p&gt;
&lt;h2 id=&quot;whats-new&quot;&gt;What’s new&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A new architecture.&lt;/strong&gt; Zephyr now supports Infineon TriCore, the architecture behind Infineon’s AURIX microcontrollers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video and multimedia.&lt;/strong&gt; Video gets a proper subsystem API, taking over the functions that used to live in the video drivers. Applications only need to change the include, to &lt;code&gt;&amp;lt;zephyr/video/video.h&amp;gt;&lt;/code&gt;. A new Multimedia Pipeline lets an application describe a media flow as sources, transforms and sinks, instead of driving each audio, video or display device itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clock monitoring.&lt;/strong&gt; A new Clock Monitor driver class observes clock frequency at run time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An automotive bus and precision timing.&lt;/strong&gt; A new LIN driver class covers the Local Interconnect Network bus used in vehicles, and a new precision timing subsystem gives drivers shared helpers for checked time arithmetic, clock operations and PI (proportional-integral) control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A native LoRaWAN stack.&lt;/strong&gt; A new backend implements LoRaWAN 1.0.x Class A directly on the LoRa radio driver, without the Semtech LoRaMac-node dependency. It supports only the EU868 region for now. If you try it, call runtime settings such as &lt;code&gt;lorawan_set_datarate()&lt;/code&gt; and &lt;code&gt;lorawan_enable_adr()&lt;/code&gt; after &lt;code&gt;lorawan_start()&lt;/code&gt;; earlier calls are refused or dropped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU affinity on every scheduler.&lt;/strong&gt; &lt;code&gt;CONFIG_SCHED_CPU_MASK&lt;/code&gt; no longer requires &lt;code&gt;SCHED_SIMPLE&lt;/code&gt;, so SMP projects can pin threads while using the scalable or multi-queue scheduler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device classes in devicetree.&lt;/strong&gt; A binding can declare a &lt;code&gt;class:&lt;/code&gt;, and code can enumerate every enabled node of that class at build time with macros such as &lt;code&gt;DT_FOREACH_CLASS_STATUS_OKAY&lt;/code&gt;. The ADC and I3C shells already use it, so they now find out-of-tree drivers too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security stack updates.&lt;/strong&gt; Mbed TLS moves to 4.1.1, TF-PSA-Crypto to 1.1.1 and TF-M to 2.3.1, which can now be built with LLVM. A new dispatch hook lets TF-PSA-Crypto route operations to a hardware accelerator, and MCUboot can embed several signing keys, so a development bootloader can boot both development- and production-signed images.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The draft also lists 130 new boards so far, and two CVEs addressed in this release, CVE-2026-8718 and CVE-2026-9263.&lt;/p&gt;
&lt;h2 id=&quot;what-will-need-changes&quot;&gt;What will need changes&lt;/h2&gt;
&lt;p&gt;The migration guide is long. These are the items most likely to reach an ordinary application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CMake 3.28 is now the minimum.&lt;/strong&gt; Ubuntu 24.04 ships 3.28.3. On Ubuntu 22.04, get a newer CMake from the Kitware APT repository or with &lt;code&gt;pip install cmake&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C17 is now the minimum C standard.&lt;/strong&gt; &lt;code&gt;CONFIG_STD_C11&lt;/code&gt;, &lt;code&gt;CONFIG_STD_C99&lt;/code&gt; and &lt;code&gt;CONFIG_STD_C90&lt;/code&gt; were removed, so a project that pins one of them fails at build time. Remove the option and build with C17 or newer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generated headers need the &lt;code&gt;zephyr/&lt;/code&gt; prefix.&lt;/strong&gt; Write &lt;code&gt;#include &amp;lt;zephyr/version.h&amp;gt;&lt;/code&gt;, not &lt;code&gt;&amp;lt;version.h&amp;gt;&lt;/code&gt;. The legacy include path that allowed the short form, &lt;code&gt;CONFIG_LEGACY_GENERATED_INCLUDE_PATH&lt;/code&gt;, is deprecated and now off by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The ring buffer API changed.&lt;/strong&gt; The zero-copy claim and finish calls give way to &lt;code&gt;ring_buf_put_ptr()&lt;/code&gt; and &lt;code&gt;ring_buf_get_ptr()&lt;/code&gt;. Code still using the old calls or the item API fails to compile without a helpful message. Enabling &lt;code&gt;CONFIG_RING_BUFFER&lt;/code&gt; brings the legacy API back while you migrate. Also, &lt;code&gt;ring_buf_get()&lt;/code&gt; no longer takes a NULL buffer to throw data away, unless &lt;code&gt;CONFIG_RING_BUFFER&lt;/code&gt; is enabled; use &lt;code&gt;ring_buf_consume()&lt;/code&gt; for that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ninja flash&lt;/code&gt; and friends are gone.&lt;/strong&gt; The CMake &lt;code&gt;flash&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;debugserver&lt;/code&gt;, &lt;code&gt;attach&lt;/code&gt; and &lt;code&gt;rtt&lt;/code&gt; targets were removed. Use the &lt;code&gt;west&lt;/code&gt; equivalents. The &lt;code&gt;run&lt;/code&gt; and &lt;code&gt;debugserver&lt;/code&gt; targets for emulators such as QEMU still work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two kernel behaviours changed.&lt;/strong&gt; &lt;code&gt;k_sem_reset()&lt;/code&gt; no longer wakes threads polling on the semaphore, and &lt;code&gt;CONFIG_SMP_BOOT_DELAY&lt;/code&gt; is replaced by a per-CPU &lt;code&gt;zephyr,deferred-start&lt;/code&gt; flag in devicetree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Some Bluetooth callbacks run in another thread.&lt;/strong&gt; Some host callbacks, such as &lt;code&gt;disconnected()&lt;/code&gt;, now run in the Bluetooth RX thread instead of the system workqueue. Check your locking and &lt;code&gt;CONFIG_BT_RX_STACK_SIZE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Old Mbed TLS options are gone.&lt;/strong&gt; Deprecated Kconfig names such as &lt;code&gt;CONFIG_MBEDTLS_TLS_VERSION_1_2&lt;/code&gt; were removed. Use the names that match Mbed TLS’s own settings, here &lt;code&gt;CONFIG_MBEDTLS_SSL_PROTO_TLS1_2&lt;/code&gt;. Unlike the old ones, they do not enable their dependencies for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Espressif boards get signed, revertible images by default.&lt;/strong&gt; Sysbuild now builds MCUboot with swap using offset and RSA-2048 signatures, using MCUboot’s development key unless you set your own. The new bootloader rejects unsigned images, so flash the bootloader and the application together when you move a device over. Swap using scratch is no longer available on boards that use Espressif’s shared partition tables, unless a board overlay adds the scratch partition back.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Board maintainers should check three more things. Revision fragments named &lt;code&gt;&amp;lt;board&amp;gt;_&amp;lt;revision&amp;gt;.conf&lt;/code&gt; are no longer read, so rename them to &lt;code&gt;&amp;lt;board&amp;gt;_&amp;lt;revision&amp;gt;_defconfig&lt;/code&gt;. NXP’s devicetree include files for LPC, Kinetis, MCX and i.MX RT have moved into per-series folders, which breaks out-of-tree board includes. And Espressif’s per-module devicetree files are gone: an out-of-tree Espressif board now includes the plain SoC file and describes its own flash.&lt;/p&gt;
&lt;h2 id=&quot;how-to-get-ready&quot;&gt;How to get ready&lt;/h2&gt;
&lt;p&gt;Build your project against &lt;code&gt;main&lt;/code&gt; now, or against RC1 once it is tagged, and read the deprecation warnings. Most of the changes above fail loudly: at build time or, for the Espressif bootloader, at first boot. Three do not: &lt;code&gt;k_sem_reset()&lt;/code&gt; used together with &lt;code&gt;k_poll()&lt;/code&gt;, &lt;code&gt;ring_buf_get()&lt;/code&gt; called with a NULL buffer in the default build, and the Bluetooth callbacks that changed threads. Check those by hand.&lt;/p&gt;
&lt;p&gt;Then read the migration guide for the subsystems you actually use. It is organised by area, so you rarely need the whole thing.&lt;/p&gt;
&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.zephyrproject.org/latest/releases/release-notes-4.5.html&quot;&gt;Zephyr 4.5 release notes, working draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.zephyrproject.org/latest/releases/migration-guide-4.5.html&quot;&gt;Migration guide to Zephyr 4.5, working draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/wiki/Release-Management&quot;&gt;Zephyr release schedule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.zephyrproject.org/latest/releases/index.html&quot;&gt;Zephyr releases and LTS policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Zephyr® is a registered trademark of The Linux Foundation. This post is independent and not endorsed by the Zephyr Project.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Engineering</category></item><item><title>On embedded, the worst bugs are often not in your code</title><link>https://mikus.io/blog/the-bug-is-not-in-your-code/</link><guid isPermaLink="true">https://mikus.io/blog/the-bug-is-not-in-your-code/</guid><description>A thread of real war stories from r/embedded shows a pattern: the bugs that cost the most time are rarely logic errors. Why embedded debugging is mostly search, and how firmware observability makes the search shorter.</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This month a thread hit the top of &lt;a href=&quot;https://www.reddit.com/r/embedded/comments/1vtszpx/share_the_funniestannoying_embedded_issue_youve/&quot;&gt;r/embedded&lt;/a&gt; with a simple prompt: share the embedded bug that cost you hours. It drew hundreds of replies. I read through about ninety of them looking for a pattern, and the pattern surprised me.&lt;/p&gt;
&lt;p&gt;Almost none of the bugs were logic errors.&lt;/p&gt;
&lt;p&gt;That thread asked for the most memorable bugs, so it is not a random sample. Most day-to-day firmware bugs are still ordinary code bugs. But the ones that eat days tend to look like these.&lt;/p&gt;
&lt;h2 id=&quot;three-real-stories&quot;&gt;Three real stories&lt;/h2&gt;
&lt;p&gt;One engineer spent a day hunting ten or twenty microamps of current draw that would not go away. The reading was maddeningly random. At one point a colleague walked over to help and the extra current vanished; they stepped away and it came back. The cause was sunlight from a window landing on a chip with an exposed die, generating a small photocurrent. A shadow fixed it. Several other people replied with the same class of story, including an ASIC that refused to power up until someone shone a flashlight on the die.&lt;/p&gt;
&lt;p&gt;Another described a board that stopped transmitting serial data whenever the air got humid. Breathe on it and it would go quiet. They suspected the humidity sensor and pulled it out entirely. The board still failed. The real cause was a cold solder joint on the enable line of the RS-232 transceiver. With the joint barely connected, the pin was effectively floating, and in humid air the leakage across the board surface pulled it to the wrong level.&lt;/p&gt;
&lt;p&gt;A third shipped a batch of devices with the wrong crystal, a few percent fast. Everything worked except a routine that detected a tone, because that was the one thing that depended on the exact sample rate. Weeks went into tracing it, because the crystal was nowhere near where the symptom showed up.&lt;/p&gt;
&lt;p&gt;A separate post the same month came from someone so tired of chasing I²C problems with an oscilloscope that they &lt;a href=&quot;https://www.reddit.com/r/embedded/comments/1vy199b/i_got_tired_of_wasting_time_chasing_simple_i%C2%B2c/&quot;&gt;built a dedicated diagnostic tool&lt;/a&gt;. The comments turned into a catalogue of the ways a two-wire bus lies to you: a target holds the clock low longer than the controller tolerates and the transaction fails as if the device were not there; the processor resets mid-byte and leaves a target holding the data line low until someone clocks it free or power-cycles it. As one commenter put it, when the data line is stuck low the fix is completely different depending on which chip is holding it, and right now everyone guesses. Another summed the bus up: I²C is barely a standard. The spec exists; the trouble is how loosely devices follow it.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The pattern&lt;/h2&gt;
&lt;p&gt;Read enough of these and the shape is always the same. The fix, when it finally arrives, is trivial. A shadow. A reflow. The right crystal. The cost was never the fix. It was the search. And the search was long because the fault was not where the symptom showed up.&lt;/p&gt;
&lt;p&gt;This is what makes embedded debugging feel different, and why it humbles people who are strong software engineers. In application software, most bugs are in logic you can inspect. You can read it, log it, step through it, reproduce it. Your whole toolkit assumes the fault lives somewhere you can look.&lt;/p&gt;
&lt;p&gt;Embedded systems break that assumption. The system is not just your code. It is your code plus a physical world your code cannot see: light, heat, humidity, a marginal solder joint, a clock a few percent off, a bus that half the devices on it implement differently. The symptom appears in software, so that is where you start looking, but the cause is usually a layer or two below, in a place your instincts never point you.&lt;/p&gt;
&lt;p&gt;That gap, between where the symptom shows and where the cause lives, is the entire debugging cost.&lt;/p&gt;
&lt;h2 id=&quot;what-actually-helps&quot;&gt;What actually helps&lt;/h2&gt;
&lt;p&gt;You cannot make the physical world stop misbehaving. What you can do is shrink the gap, by making the invisible parts of the system visible. That is really what firmware observability is: not a product category, but the practice of getting the system to tell you what it is doing so you spend less time guessing.&lt;/p&gt;
&lt;p&gt;A few things move the needle, roughly in the order they start helping:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;See the physical layer directly.&lt;/strong&gt; A logic analyzer on the bus turns “the sensor is flaky” into decoded transactions you can read, and a scope shows the analog faults a logic analyzer hides, like slow edges from weak pull-ups. The person who built that I²C tool was doing exactly this, just packaged so they never had to set up the scope again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capture the crash instead of losing it.&lt;/strong&gt; In the field, a fault usually ends in a reset, from a watchdog or a reset-on-fault handler, and the RAM state that would explain it is gone. A fault handler that saves a coredump to flash means the one crash you did catch still tells you something after the reset. On Zephyr, the coredump subsystem writing to a flash partition, plus GDB on the host, gets you a backtrace after the device has rebooted, as long as you kept the &lt;code&gt;zephyr.elf&lt;/code&gt; from the build that crashed. On the bench, a probe channel like SEGGER RTT lets you watch the device live without a spare UART.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get logs off the device without changing the timing.&lt;/strong&gt; The classic embedded trap is that adding a print statement moves the bug. Loggers that move formatting off the device, such as Rust’s &lt;code&gt;defmt&lt;/code&gt; or Zephyr’s dictionary-based logging, send references to the format strings and the raw arguments, and let the host build the strings, which keeps the on-device cost small. The host can only do that with a file from the exact build running on the device: the ELF for &lt;code&gt;defmt&lt;/code&gt;, and for Zephyr the &lt;code&gt;log_dictionary.json&lt;/code&gt; that the build writes next to the image. Zephyr’s &lt;a href=&quot;https://docs.zephyrproject.org/latest/services/logging/index.html#dictionary-based-logging&quot;&gt;logging documentation&lt;/a&gt; says the dictionary only works with the build that produced it, so archive it with every image you ship; rebuilding the same source later is not a safe substitute. It also pays to have each device log an identifier for its build, for example at boot, so a log from the field points straight to the right archived files. Trace tools like SEGGER SystemView or Percepio Tracealyzer show you the ordering of interrupts and tasks that a print can never capture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For the bugs that only happen in the field, bring the field to you.&lt;/strong&gt; The hardest stories above share a trait: they showed up on one unit, in one place, under conditions you cannot reproduce on the bench. This is where firmware observability platforms earn their place. They ship crash reports, metrics, and logs home from deployed hardware, so you can see the state that led to a fault without standing next to the device. &lt;a href=&quot;https://memfault.com&quot;&gt;Memfault&lt;/a&gt; collects coredumps, metrics, and logs, and adds OTA updates. &lt;a href=&quot;https://golioth.io&quot;&gt;Golioth&lt;/a&gt; is a broader device platform that covers remote logs, diagnostics, and OTA, and hands crash analysis to Memfault through an integration. &lt;a href=&quot;https://spotflow.io&quot;&gt;Spotflow&lt;/a&gt;, where I work, collects logs, metrics, and core dumps with stack traces, with ready-made integrations for Zephyr, ESP-IDF, and Nordic’s nRF Connect SDK. It sends data over MQTT and buffers it on the device while the connection is down, which suits devices that are only online now and then, and it also handles OTA updates and tracks known CVEs in your firmware. I am telling you that I work there so you can weigh the recommendation accordingly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this stops the wrong-crystal bug from happening. What it changes is how long you spend in the dark before you find it.&lt;/p&gt;
&lt;h2 id=&quot;the-real-skill&quot;&gt;The real skill&lt;/h2&gt;
&lt;p&gt;So here is the thing I would tell someone new to embedded, and the reason that thread stuck with me. The engineers who are fast at this are not the ones who write bug-free firmware. Nobody does. They are the ones who have learned, usually the hard way, that the bug is probably not where they are looking, and who have built enough visibility into the system that the search is short.&lt;/p&gt;
&lt;p&gt;You cannot add that visibility at three in the morning, with a customer’s device failing in a building you cannot get to. You add it before, on purpose, while everything still works. That is the discipline. The debugging skill is really a seeing skill.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Updated 4 October 2026: thanks to &lt;a href=&quot;https://x.com/ManBoEmbedded&quot;&gt;@ManBoEmbedded&lt;/a&gt; on X for pointing out that the logging dictionary has to be archived with every build, and that logs should name the build they came from. The logging and coredump advice above now covers both.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Engineering</category></item><item><title>Cloudflare Pages already keeps your preview deploys out of Google</title><link>https://mikus.io/blog/branch-previews-astro-cloudflare/</link><guid isPermaLink="true">https://mikus.io/blog/branch-previews-astro-cloudflare/</guid><description>I set up per-branch preview deployments on Cloudflare Pages and reached for an Astro noindex conditional to keep them out of search. I did not need it: Cloudflare already noindexes previews at the HTTP layer. A note on where infrastructure logic belongs.</description><pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wanted to review every change to this site on a real URL before it went public. Not localhost. The actual rendered page, deployed, so I could catch the layout problems a dev server hides.&lt;/p&gt;
&lt;p&gt;Cloudflare Pages gives you that: push a branch, get a preview deployment at its own URL. But those preview URLs are public, and my first thought was the obvious one. I don’t want half-finished drafts turning up in Google. So I reached for an Astro conditional to stamp &lt;code&gt;noindex&lt;/code&gt; on preview builds.&lt;/p&gt;
&lt;p&gt;Then I checked whether I actually needed it. I didn’t. Cloudflare already does it, one layer below my application, and the reason my clever conditional was pointless is the interesting part of this post.&lt;/p&gt;
&lt;h2 id=&quot;the-setup-two-systems-one-job-each&quot;&gt;The setup: two systems, one job each&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; deploys. Production from &lt;code&gt;main&lt;/code&gt;, a preview for the other branches it is configured to build. It deploys every branch that builds, good change or bad, because a preview is for looking at. It runs no checks beyond the build command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; is the quality gate. It runs format, type-check, and build on every pull request, and deploys nothing. It is what blocks a bad merge, once its job is a required status check in a branch protection rule or ruleset.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Connect the repo, set the build command (&lt;code&gt;pnpm build&lt;/code&gt;) and output directory (&lt;code&gt;dist&lt;/code&gt;), and choose the production branch (&lt;code&gt;main&lt;/code&gt; here, but that is a project setting, not a rule). By default Cloudflare deploys your non-production branches as previews, and a pull request opened from the repository gets a preview URL posted on it. You can scope this to all branches, none, or a chosen set. Each preview gets an immutable hash URL and a per-branch alias.&lt;/p&gt;
&lt;p&gt;That is the whole deploy configuration. Now the part I got wrong.&lt;/p&gt;
&lt;h2 id=&quot;the-noindex-i-didnt-need-to-write&quot;&gt;The noindex I didn’t need to write&lt;/h2&gt;
&lt;p&gt;Here is the code I was about to add to my base layout:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; isPreview&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  process.env.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;CF_PAGES&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;1&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;CF_PAGES_BRANCH&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !==&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;main&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  {isPreview &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;robots&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;noindex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&amp;gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is not wrong. Cloudflare injects &lt;code&gt;CF_PAGES=1&lt;/code&gt;, &lt;code&gt;CF_PAGES_BRANCH&lt;/code&gt;, &lt;code&gt;CF_PAGES_URL&lt;/code&gt;, and &lt;code&gt;CF_PAGES_COMMIT_SHA&lt;/code&gt; into every Pages build, so at build time a static Astro site can tell it is a preview and emit the tag. The mechanism is real.&lt;/p&gt;
&lt;p&gt;It is just unnecessary, because Cloudflare already handles this a layer down. From their docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By default, every preview deployment generated by Cloudflare Pages includes the &lt;code&gt;X-Robots-Tag: noindex&lt;/code&gt; HTTP response header.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can watch it happen. Curl a preview URL and the production URL and compare headers:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; curl&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -sI&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; https://&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;.my-project.pages.dev&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; robots&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;x-robots-tag:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; noindex&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; curl&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -sI&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; https://my-project.pages.dev&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; robots&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;# (nothing)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview deployments carry &lt;code&gt;noindex&lt;/code&gt;. Production does not. I had not written a line of code, and the exact behavior I wanted was already there.&lt;/p&gt;
&lt;h2 id=&quot;why-the-header-is-the-right-place-and-my-meta-tag-was-the-wrong-one&quot;&gt;Why the header is the right place and my meta tag was the wrong one&lt;/h2&gt;
&lt;p&gt;There are two ways to tell a crawler to skip a page:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;robots&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;noindex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;X-Robots-Tag&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; noindex&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They mean the same thing to Google, but they live at different layers. The meta tag exists only inside HTML that you generated. The HTTP header applies to the response itself, every response, whatever your app rendered and whether or not it is even HTML.&lt;/p&gt;
&lt;p&gt;That difference is the whole point. Cloudflare knows a deployment is a preview because &lt;em&gt;it&lt;/em&gt; created the preview. It does not need my application to re-derive that fact from an environment variable and act on it. The system that owns the fact applies the correct HTTP semantics, and my app never has to care.&lt;/p&gt;
&lt;p&gt;That is the lesson worth keeping: &lt;strong&gt;do not solve an infrastructure problem inside your application.&lt;/strong&gt; My &lt;code&gt;isPreview&lt;/code&gt; check was pulling a fact that belongs to the deploy platform up into my page templates. It would only have covered HTML rendered through that layout, not images, PDFs, or any page that skips it. Cloudflare already answered the question, once, at the edge, for every response.&lt;/p&gt;
&lt;p&gt;A static &lt;code&gt;_headers&lt;/code&gt; file can also set &lt;code&gt;X-Robots-Tag&lt;/code&gt; by hostname, using placeholders: &lt;code&gt;https://:project.pages.dev/*&lt;/code&gt; for the production &lt;code&gt;pages.dev&lt;/code&gt; address and &lt;code&gt;https://:version.:project.pages.dev/*&lt;/code&gt; for previews, if you ever need indexing rules the defaults do not cover. For keeping previews out of search, you do not need it.&lt;/p&gt;
&lt;h2 id=&quot;the-layers-in-one-picture&quot;&gt;The layers, in one picture&lt;/h2&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    git push / PR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ┌────────────┴────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                         ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       GitHub Actions            Cloudflare Pages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       (quality gate)               (deploy)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                    ┌────┴────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   format / check / build         ▼         ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                  main      branch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             ▼                    │         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        pass → merge              ▼         ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              production  preview&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  │         │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              indexable   X-Robots-Tag:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                            noindex&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cloudflare deploys and decides indexing. GitHub Actions decides whether a commit is allowed to become production. Neither job belongs in my page templates.&lt;/p&gt;
&lt;h2 id=&quot;the-one-thing-cloudflare-doesnt-do-for-you&quot;&gt;The one thing Cloudflare doesn’t do for you&lt;/h2&gt;
&lt;p&gt;Cloudflare noindexes &lt;em&gt;previews&lt;/em&gt;, but your &lt;em&gt;production&lt;/em&gt; &lt;code&gt;*.pages.dev&lt;/code&gt; stays indexable. If you also serve the site on a custom domain, &lt;code&gt;my-project.pages.dev&lt;/code&gt; and &lt;code&gt;mydomain.com&lt;/code&gt; are now the same content at two addresses, which is a duplicate-content problem. The fix is not a preview conditional. It is a canonical URL on every page pointing at your real domain (this site sets one in its base layout), which search engines treat as a strong hint, or, to settle it, a Bulk Redirect from the &lt;code&gt;pages.dev&lt;/code&gt; hostname to your domain. The &lt;code&gt;_redirects&lt;/code&gt; file cannot match on hostname, so it cannot do this one.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;noindex&lt;/code&gt; is not access control. Anyone with a preview URL can still open it. If a draft must stay private, put the previews behind Cloudflare Access.&lt;/p&gt;
&lt;h2 id=&quot;the-quality-gate&quot;&gt;The quality gate&lt;/h2&gt;
&lt;p&gt;The deploy side does none of the checking, so a separate workflow runs the same commands I run locally, and a red check blocks the merge once the job is marked as a required status check.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;CI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;  push&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;    branches&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;  pull_request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;concurrency&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;  group&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;ci-${{ github.ref }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;  cancel-in-progress&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;  build&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm/action-setup@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;          node-version&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;22&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;          cache&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm install --frozen-lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm check-format&lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt; # prettier --check .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm check&lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt; # astro check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;pnpm build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pnpm/action-setup&lt;/code&gt; reads the pnpm version from the &lt;code&gt;packageManager&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt;; without that field, give it a &lt;code&gt;version&lt;/code&gt;. &lt;code&gt;--frozen-lockfile&lt;/code&gt; fails the install if the lockfile is out of date, so CI builds the exact tree I committed. The three checks are the same scripts in &lt;code&gt;package.json&lt;/code&gt;, so &lt;code&gt;pnpm check-format &amp;amp;&amp;amp; pnpm check &amp;amp;&amp;amp; pnpm build&lt;/code&gt; locally means green in CI. &lt;code&gt;cancel-in-progress&lt;/code&gt; kills a superseded run, so a burst of commits only spends minutes on the last one.&lt;/p&gt;
&lt;h2 id=&quot;what-i-actually-learned&quot;&gt;What I actually learned&lt;/h2&gt;
&lt;p&gt;I came in ready to write application code to keep previews out of Google, and the useful outcome was not writing it. Cloudflare deploys previews and marks them &lt;code&gt;noindex&lt;/code&gt;. GitHub Actions gates the merge. The site sets a canonical URL. Three layers, each owning one job, and none of them my page templates.&lt;/p&gt;
&lt;p&gt;The best infrastructure code is often the code you were about to write and then didn’t have to.&lt;/p&gt;
</content:encoded><category>Engineering</category></item></channel></rss>