<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Sebastian Stöhr — Blog</title>
        <link>https://www.sebastianstoehr.de/blog/</link>
        <description>Perspectives on engineering management and AI transformation</description>
        <lastBuildDate>Mon, 25 May 2026 05:48:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator></generator>
        <language>en</language>
        <copyright>© 2026 Sebastian Stöhr</copyright>
        <atom:link href="https://www.sebastianstoehr.de/blog/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[The Confluence Graveyard: How to Turn Scattered Data into AI Intelligence]]></title>
            <link>https://www.sebastianstoehr.de/blog/turn-scattered-data-into-ai-intelligence/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/turn-scattered-data-into-ai-intelligence/</guid>
            <pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Stop drowning in fragmented data and start building actionable intelligence. Learn how a specialized AI workflow can transform scattered documentation into a structured "source of truth," moving beyond simple search to enable deep organizational reasoning.]]></description>
            <content:encoded><![CDATA[<p>We are drowning in data but starving for knowledge. In most organizations, the "source of truth" doesn't exist in one place.
It’s a fragmented puzzle scattered across JIRA tickets, chat threads, outdated Confluence pages, and email chains.</p>
<p>Standard RAG (Retrieval-Augmented Generation) attempts to solve this by dumping raw data into a vector database. But there’s a flaw: If the input is messy, the AI’s reasoning will be too.
To unlock the real potential of LLMs, we need to move from Retrieval to Structured Synthesis.</p>
<h3>Two Skills, One Source of Truth</h3>
<p>I am currently developing a workflow that bypasses the "Documentation Debt" by using two specialized AI roles: The Archaeologist and The Librarian.</p>
<h4>Skill 1: The Archaeologist (The Synthesizer) 🦴</h4>
<p>The Archaeologist doesn't just read; it reconstructs. Using the Model Context Protocol (MCP), it connects directly to JIRA, Confluence, and other documents to "dig" through history.</p>
<p>Instead of just returning a search result, it performs a Temporal Synthesis:</p>
<ul>
<li><strong>Merging History:</strong> It identifies different versions of a project plan and merges them into a single "Current Truth" file.</li>
<li><strong>The "Why" Factor:</strong> It extracts the rationale behind decisions that are often lost when a ticket is closed.</li>
<li><strong>Source Integrity:</strong> Every claim is back-linked to the original data source for human verification.</li>
</ul>
<p>The output is a clean, domain-specific Markdown file that represents the state of a topic right now - and includes a change log with important milestones.</p>
<h4>Skill 2: The Librarian (The Architect) 📚</h4>
<p>A collection of files is just a digital pile. To make knowledge actionable, the AI needs to understand relationships.
The Librarian processes the Archaeologist’s files and builds a Knowledge Graph.</p>
<p>It identifies business-critical links, for example:</p>
<ul>
<li>Product A is the successor to Product B.</li>
<li>Feature X is an add-on that requires Service Y.</li>
<li>Sales Campaign Z targeted the users of Product B.</li>
</ul>
<p>By interlinking these topics in a structured Markdown Wiki, the AI can navigate the business ecosystem like a seasoned employee, not a blind search engine.</p>
<p><em>By using tools like Obsidian you can easily navigate through these files - and also generate visualizations to see, how topic related to each other.</em></p>
<h3>Real-World Value: From Search to Reasoning</h3>
<p>Imagine asking an AI: "Why did our sales for Product B dip in Q3?"
A standard RAG might just give you a sales report. An AI backed by properly structured information knows:</p>
<ul>
<li>Which changes happened during Q3</li>
<li>Which sales campaigns happened</li>
<li>Which products were effected - incl. which products are related and might be effected indirectly</li>
<li>...</li>
</ul>
<p>This is the shift from "Finding Information" to "Explaining Effects" - not stopping at the obvious findings but digging deeper.</p>
<h3>The Road Ahead</h3>
<p>This isn’t a one-time cleanup; it’s a continuous, automated process. As new tickets are created and docs are updated, the "Current Truth" evolves.
The same skills can continuously process new information and keep the knowledge base intact.</p>
<p>Over the coming weeks, I’ll be refining the automation of these skills and exploring the best storage architectures.
Right now we are consuming the linked Markdown files directly.
As agents get more and more skilled in scanning through filesystems (a typical scenario for coding tasks), this works pretty well right now.
We'll investigate options how we share this knowledge across teams, e.g. utilizing graph databases or by sticking to version-controlled Markdown repositories.</p>
<p><em>I’m curious: How are you ensuring your AI assistants are looking at the "Current Truth" rather than just the "first doc it can find"?</em></p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/_astro/turn-scattered-data-into-ai-intelligence.DOdqRDHX.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Building an iOS app in just a day]]></title>
            <link>https://www.sebastianstoehr.de/blog/building-ios-app-in-a-day/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/building-ios-app-in-a-day/</guid>
            <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover how AI shifts the development process from mere coding to rapid requirements engineering and architectural sparring. This article explores a real-world experiment of building a functional, AI-powered iOS app.]]></description>
            <content:encoded><![CDATA[<p><strong>I built a full iOS app in one day.</strong></p>
<p>The world doesn’t need another calorie tracking app - yet I decided to build one.</p>
<p>I was looking for something quite specific: a motivating diet coach that helps me stay in a calorie deficit without feeling like I am reporting to a spreadsheet.
Something lightweight. Encouraging. Fast to use. Integrated into the Apple ecosystem. And ideally with a bit more personality than the usual “you have 327 kcal remaining” experience.</p>
<p>As I’m currently exploring different AI workflows across the whole process from idea to deployment, this became a good real-world test case.</p>
<hr>
<h4>The experiment</h4>
<p>The result was an iOS calorie deficit tracker with a fox mascot named Carlo.</p>
<p>The scope was intentionally non-trivial:</p>
<ul>
<li>SwiftUI app incl. widgets and watch app</li>
<li>AI-powered meal scanning</li>
<li>HealthKit integration</li>
<li>onboarding flows</li>
<li>localization in two languages</li>
<li>17 work packages, planned and executed</li>
</ul>
<p>For context: I write code, but I had not implemented an iOS app hands-on for more than six years. Last time, UIKit was still my default mental model.
So this was a useful test case: enough engineering experience to judge the output, but enough platform distance to feel where AI actually helps.</p>
<hr>
<h4>The biggest learning</h4>
<p>The most valuable AI contribution was not code generation. It was requirements engineering.</p>
<p>Before writing a single line of code, I used Claude as a sparring partner. It interviewed me through:
the problem, the user experience, the scope, the edge cases, the data model, the architecture, the sequencing of the work</p>
<p>That changed the project: Instead of jumping straight into SwiftUI, I stayed in thinking mode longer. The idea became clearer. The boundaries became sharper. The work became executable.</p>
<p>The acceleration of implementation makes one thing even more important than before: Being clear about what exactly to build.</p>
<hr>
<h4>The workflow: Claude and Codex</h4>
<p>I split the work deliberately:</p>
<ul>
<li>Claude for product framing, requirements, user interface design, architecture, and critique</li>
<li>Codex for implementation</li>
<li>ChatGPT for generation of the visuals</li>
</ul>
<p>Claude helped me ask:</p>
<ul>
<li>Are we solving the right problem?</li>
<li>What are the hidden assumptions?</li>
<li>What should be in scope today?</li>
<li>What should explicitly not be in scope?</li>
<li>Where could the architecture become fragile?</li>
</ul>
<p>Codex helped me move through the implementation work packages quickly.</p>
<hr>
<h4>The human role</h4>
<p>The human role did not disappear. It shifted.</p>
<p>I was still responsible for:</p>
<ul>
<li>defining the product intent</li>
<li>deciding what good enough means, reviewing trade-offs</li>
<li>cutting scope</li>
<li>validating implementation choices</li>
<li>keeping the system coherent</li>
</ul>
<p>AI-assisted development is not just faster coding. It changes the shape of the delivery process.</p>
<p>The first big productivity gain was not: “write code faster.”
It was: “get from vague need to structured, sequenced, implementable work faster.”</p>
<hr>
<h4>The bigger takeaway</h4>
<p>Using AI for coding speeds up development. But the real leverage comes from integrating AI into the whole development process:</p>
<ul>
<li>requirements</li>
<li>critique</li>
<li>architectural sparring</li>
<li>scope control</li>
<li>turning ambiguity into executable work</li>
</ul>
<p>I have been using the app for a week now — and I actually enjoy it.</p>
<p>Even more importantly, it was genuinely fun to develop software again. That is a feeling I had lost for a while.</p>
<p>With AI used deliberately, you can iterate extremely fast, focus on the important parts, and orchestrate more work than would otherwise be realistic in such a short time.</p>
<p>But AI is not magic. It does not remove the need for product thinking or engineering judgment.</p>
<p>It amplifies the people who bring those things into the workflow.</p>
<p><strong>For me, that is the real lesson: The future of AI-assisted software development is not just about faster implementation. It is about redesigning the way we move from idea to working product.</strong></p>
<p><strong>Guiding people and teams through this change and shaping new processes and ways of collaboration is the important leadership task right now.</strong></p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/_astro/building-ios-app-in-a-day-2.DSEu2r2G.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Splitting a 20+ Year Old Monolith]]></title>
            <link>https://www.sebastianstoehr.de/blog/splitting-the-monolith/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/splitting-the-monolith/</guid>
            <pubDate>Sun, 30 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[How we decomposed a decades-old monolithic application into focused services — without derailing ongoing project delivery.]]></description>
            <content:encoded><![CDATA[<p>The codebase had been running in production for over two decades. It worked, but we faced more and more problems.</p>
<p>Every new feature had to be woven into a framework that nobody outside the company had ever heard of — an in-house invention from a different era of software development. Onboarding new developers took months before they could ship anything meaningful. Teams coordinating across the monolith had to synchronise constantly, because a change in one corner could quietly break something in another. In the last years we had improved the system a lot. It wasn't failing, but it held us back.</p>
<p>The question wasn't whether to modernise. That decision was inadvisable. The question was how to do it without turning every ongoing project into a migration risk.</p>
<h2>The trap we wanted to avoid</h2>
<p>The "big rewrite" is appealing in theory. You draw a clean architectural diagram, you stand up new infrastructure, you declare a migration timeline. And then reality arrives: the old system keeps getting features added because the business can't pause, the new system is always six months from ready, and eventually you're maintaining two full codebases at once. Teams get split between the legacy world and the new one. Morale suffers. Timelines slip.</p>
<p>We'd seen this pattern before. We didn't want to repeat it.</p>
<p>Instead, we set a constraint: the modernisation work had to happen <em>through</em> normal project delivery, not alongside it. If a feature was being built anyway, that was the opportunity to build it on the new stack. The monolith would shrink incrementally — not because we scheduled a migration, but because we made the right choice the obvious choice each time a project started.</p>
<h2>Making the new stack the easier path</h2>
<p>This is where the approach got interesting. Choosing a technology is not the same as making it adoptable.</p>
<p>We moved to Spring Boot — a widely understood framework with a large ecosystem and, importantly, a far gentler learning curve than what we'd been working with. That part was deliberate. We didn't want to bet the company on a completely different stack we didn't understand yet, just because it looked modern on a conference slide. The change was already hard enough. Keeping the technology familiar let us reduce one category of risk while dealing with all the others.</p>
<p>But we didn't just hand teams a blank Spring Boot project and wish them luck. We built Spring Boot Starters for the patterns that would repeat across every new application: authentication, logging, shared configuration, common integrations. The infrastructure decisions were already made. Teams could focus on the actual business logic.</p>
<p>That initial investment was real. It took time to make the new stack the easier path. It was a team effort, and there were doubts along the way because, in the beginning, the old path was still the known path. We were effectively applying the Strangler Fig pattern: keep the existing system alive, grow the new capabilities around it, and make sure each new piece reduces the surface area of the monolith instead of creating a second system beside it.</p>
<p>This mattered more than it might sound. The productivity gap between the old and new stack was significant — not because Spring Boot is magic, but because developers could move in a framework they recognised, with community resources available, without navigating an undocumented internal abstraction layer. Features that would have taken weeks took days. That margin was what made it possible to absorb the migration overhead within project timelines. We weren't asking teams to do extra work — we were giving them back time.</p>
<p>The starters also enforced consistency without requiring it through policy. New services followed the same patterns not because someone reviewed every PR for compliance, but because the starter was the starting point. That kind of structural guardrail is worth more than a style guide.</p>
<p>At some point we crossed a turning point. Building things the new way was no longer the disciplined choice; it was simply the easier and more enjoyable one. Developer experience improved, turnaround times got shorter, and teams could move with less ceremony. Once that happened, the migration stopped feeling like a burden and started compounding on its own.</p>
<h2>What was actually hard</h2>
<p>The coordination problem didn't disappear — it just changed shape.</p>
<p>Splitting a monolith means drawing boundaries. And drawing boundaries in a 20-year-old system means confronting twenty years of accumulated coupling. Some of it was intentional — shared data models, deliberately reused business logic. Some of it was accidental — things that had grown together over time because it was easier than separating them. Untangling the two required a lot of careful decisions about what belonged where, and not all of those decisions were obvious.</p>
<p>There were also moments where the pressure to ship something on the new stack had to be balanced against the risk of building the boundary in the wrong place. A service boundary is much harder to move than a class boundary. We got some of them right on the first try. Others needed revisiting.</p>
<p>One coincidence helped us. We were already in the middle of a major technical renovation of the backend, which meant touching a lot of the system anyway. In that context, introducing proper abstractions and extracting clear services often wasn't much more work than doing the renovation in place inside the monolith. Once you have to open the walls, adding structure is cheaper than it looks from the outside.</p>
<p>And the old system kept running throughout. Which meant keeping it stable, handling bugs, and doing so without the attention it would have received if it had been the only thing people were working on. That's the hidden cost of incremental migration: the legacy system doesn't get to wind down — it just slowly gets smaller while still requiring care.</p>
<h2>The one thing worth taking from this</h2>
<p>If you're facing something similar, the most useful frame is probably this: the goal isn't to migrate — it's to make the new thing so much easier to work in that migration happens naturally as a byproduct of normal work.</p>
<p>That requires investing in the developer experience of the new stack early. Not documentation, not training sessions — actual tooling, starters, templates that remove friction. When developers can be productive faster on the new stack than the old one, the architectural migration mostly takes care of itself. It becomes the path of least resistance, which is the only sustainable kind of path.</p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/images/og/blog/en/splitting-the-monolith.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Merging Frontend Teams Without Breaking Them]]></title>
            <link>https://www.sebastianstoehr.de/blog/frontend-consolidation/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/frontend-consolidation/</guid>
            <pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[When merging frontend teams, the hardest part isn't the tech stack — it's getting teams to actually want to make it work.]]></description>
            <content:encoded><![CDATA[<p>The conversation that started this whole initiative wasn't about architecture. It was about duplication — specifically, several teams across the company maintaining effectively the same frontend, built with slightly different choices, owned by groups that had grown up independently and had little reason to talk to each other.</p>
<p>The business case for consolidation was obvious. The human case was not.</p>
<h2>The resistance was real</h2>
<p>Most teams don't love being told that what they've built needs to be merged into something else. Even when the rationale is sound, the instinct is defensive: <em>our way works, the other team's approach is messier, this is going to slow us down</em>. That's not unreasonable — these teams had shipped real things, had opinions forged from experience, and weren't wrong that consolidation carries risk.</p>
<p>What I've learned is that the moment you treat this as a purely technical problem, you've already made it harder. You can produce the most well-reasoned tech strategy in the world, but if the teams who have to execute it don't feel ownership over it, the implementation will be slow, half-hearted, or quietly subverted. Not out of malice — just out of the natural friction that comes from having something imposed rather than decided.</p>
<p>So before we talked seriously about architecture, we talked to people.</p>
<h2>Making space for the real conversations</h2>
<p>The approach we took was to use structured workshop formats to give every affected team a seat at the table. Not a feedback round where decisions had already been made — actual working sessions where the direction was genuinely still open.</p>
<p>In practice, this meant bringing representatives from each team together to map out what existed, what was working, what wasn't, and what everyone was worried about. We were explicit that there was no predetermined answer. That built credibility. People showed up differently when they believed their input would actually shape the outcome, rather than just being documented before a decision landed from above.</p>
<p>We ran alignment sessions where teams could challenge each other's assumptions directly — which occasionally got uncomfortable, but produced better results than any steering committee would have. The goal wasn't harmony. It was a shared understanding of the constraints and trade-offs, so that when a technical direction was eventually proposed, the reasoning was legible to everyone who had been in the room.</p>
<p>The result was a common technical strategy that all the involved teams were genuinely motivated to tackle. That last part matters more than it sounds.</p>
<h2>The technical decision followed from the constraints</h2>
<p>Once you have a coalition of teams with real skin in the game, the architectural conversation changes. The question shifts from "what's the best technical solution?" to "what can we actually execute together, across teams, without stopping everything else we're doing?"</p>
<p>That framing led us naturally to a micro-frontend strategy. Not because it's fashionable, but because it was the only approach that fit the reality: multiple teams, distributed across locations, each with ongoing product commitments, none of whom could dedicate six months to a big-bang migration.</p>
<p>Micro-frontends allowed us to merge the applications iteratively. Teams could contribute incrementally, applying the shared approach to new features or defined areas of their codebase while continuing other work. Synergies became available almost immediately — a shared component didn't have to wait for a full merger to be useful. And no single team carried the whole migration burden, which was important for keeping people engaged over what was going to be a long-running effort.</p>
<h2>What actually made it work</h2>
<p>Looking back, the thing that mattered most wasn't the choice of architecture. Micro-frontends solved the delivery problem, but the strategy would have stalled at the first real conflict if the teams hadn't trusted each other and the process.</p>
<p>The workshops were the lever. They were slower than just deciding and announcing. They required facilitating real disagreements rather than smoothing them over. But they produced something a top-down mandate cannot: people who felt they had made the decision and were fully committed to the plan.</p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/images/og/blog/en/frontend-consolidation.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a Design System Across Teams]]></title>
            <link>https://www.sebastianstoehr.de/blog/building-a-design-system/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/building-a-design-system/</guid>
            <pubDate>Fri, 01 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[How we built a shared design system for all IONOS customer frontends — and what it took to make UX and engineering actually share ownership of it.]]></description>
            <content:encoded><![CDATA[<p>The symptom was obvious before the cause was. Different parts of the product looked slightly different. Not dramatically — a slightly different button shape here, a different modal behaviour there, slightly inconsistent spacing. Nothing that would make the front page of a design blog. But for users moving through the product, and for designers trying to maintain any coherent visual language across it, the drift was real.</p>
<p>The underlying cause was structural. Different frontend teams had built their UIs in parallel, each making local decisions that made sense in context but compounded over time. When a designer wanted to update something — a hover state, a form pattern, a component layout — the change had to be implemented in multiple places by multiple teams, often inconsistently, always slowly.</p>
<p>A central design system was the obvious answer. What was less obvious was how to build it in a way that teams would actually adopt.</p>
<h2>The collaboration problem</h2>
<p>Most design system efforts fail not because of the technology, but because of the ownership model. Engineering builds a component library, hands it to designers, and neither side feels fully responsible for it. Or the design side defines everything in Figma and engineering spends most of its time translating specs into components that don't quite match. The system becomes a negotiation surface instead of shared infrastructure.</p>
<p>We started with what looked like a technical problem: standardising the code that already existed and gradually extracting a shared system from it in close collaboration with UX. The goal wasn't to design an ideal system in isolation. It was to define one teams could adopt incrementally, with a migration path that was clear enough to work in real product delivery.</p>
<p>That became especially important because the work coincided with a large rebranding effort, moving customer frontends from the 1&#x26;1 brand to IONOS. Most applications had to be touched anyway. That gave us a narrow window to establish a stronger foundation for the future while also reducing the amount of one-off effort the rebrand would otherwise have required.</p>
<h2>A technology choice driven by constraints</h2>
<p>The first version of the design system was built with SASS, PostCSS, and Vanilla JS, delivered via CDN.</p>
<p>This wasn't an accident. IONOS had frontend teams working across different technologies — some on older stacks, some on newer frameworks. If we'd built the design system as a React component library, we would have solved the problem for React teams and created a new one for everyone else. A CDN-delivered bundle with no framework dependency could be adopted by any team, regardless of their tech stack. The design system could be a shared foundation precisely because it didn't impose a technology choice.</p>
<p>The tradeoff was encapsulation. CSS delivered through a CDN is global. You could include the design system and, if you weren't careful, its styles would bleed into your application's own styles. Component behaviour was JavaScript operating on DOM elements in the usual way — straightforward, but without the clean isolation that modern component models provide. For a first version, it was the right call. For a maturing system with more teams and more complexity, it wasn't going to scale.</p>
<h2>What the first version revealed</h2>
<p>The v1 approach taught us something important: a design system is not a project you finish. It's infrastructure that has to evolve with the product and the teams using it.</p>
<p>As adoption grew, so did the friction points. Teams reported style conflicts. Updating a component required coordination to ensure nothing broke downstream. The lack of encapsulation, which had been manageable with a few teams, became a genuine source of instability with more.</p>
<p>The next version of the design system is being built with Stencil.js — a framework for building native web components. Shadow DOM provides the encapsulation the first version was missing: component styles live within the component, not in the global stylesheet. Lazy loading means teams only pay the cost of the components they actually use, rather than loading the full bundle. And because web components are a browser standard, the framework-agnostic principle from v1 carries forward: any frontend technology can consume them.</p>
<p>The move to Stencil wasn't a rejection of the first approach. The first approach was a mandatory step on our journey, shaping collaboration and laying the foundation for the next chapter.</p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/images/og/blog/en/building-a-design-system.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Third-Party JavaScript — The Right Way]]></title>
            <link>https://www.sebastianstoehr.de/blog/third-party-javascript/</link>
            <guid isPermaLink="false">https://www.sebastianstoehr.de/blog/third-party-javascript/</guid>
            <pubDate>Sun, 06 Aug 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[Five rules for writing JavaScript that runs safely inside someone else's website — learned from building integrations at 1&1.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Note: Published in 2017. While the core principles remain valid, web development has evolved. You can now leverage modern features like JavaScript modules to handle third-party scripts more efficiently.</p>
</blockquote>
<p>When you work at a company like 1&#x26;1, a lot of the JavaScript you write doesn't run on pages you control. It runs on different websites, inside CMSs you've never seen, alongside other scripts you know nothing about. That changes what good code looks like.</p>
<p>The framing that helped me most: a website has two parties — the operator and the visitor. When you ship JavaScript that integrates into someone else's site (a social share button, an analytics snippet, an embeddable widget), you're the third party. Your code is a guest. Guests don't rearrange the furniture.</p>
<p>Most of what follows is obvious in retrospect, but I've seen each of these problems cause real damage in production. So here are the rules I kept coming back to.</p>
<h2>Rule 0: It's not your page</h2>
<p>Everything else is downstream of this one. You have no control over the site your code lands on. You don't know what else is running, what the DOM looks like, or how the developer who integrated you structured their HTML. You can't assume anything is where you expect it to be.</p>
<p>This sounds straightforward, but it changes the disposition you bring to every line of code. If a function might throw because of something on the host page, handle it. If a DOM node might be missing, don't assume it's there. If your script crashes, the whole page might crash with it — and that's a far worse outcome than your feature not loading.</p>
<h2>Rule 1: No global variables</h2>
<p>Global variables are shared state on the host page. Any name you claim is a name some other script — or the page itself — might also want to use.</p>
<p>The fix is simple: wrap everything in an IIFE and use local variables for everything.</p>
<pre><code class="language-js">(function(window, document, undefined) {
  // all your code lives here
  // local variables only
}(window, document))
</code></pre>
<p>This pattern gets you a clean local scope. <code>window</code> and <code>document</code> are passed in explicitly, which has the side benefit of making them local references — slightly faster to look up, and easier to minify. <code>undefined</code> is listed as a parameter but never passed an argument, which is the pre-ES5 trick for guaranteeing it actually means <code>undefined</code> in environments where it could theoretically be overwritten.</p>
<p>A linter like ESLint will catch accidental globals before they ship. Worth setting up from the start.</p>
<h2>Rule 2: Use your own namespace</h2>
<p>Sometimes you genuinely need to expose a public API — a function the integrating site can call to configure your widget, or an object they interact with. That means you need one global. One, not several.</p>
<p>Export everything as a single object with a name that's specific enough to be yours. Facebook uses <code>FB</code>. Twitter uses <code>twttr</code>. The naming logic isn't clever — it's just unlikely to collide with something else on the page. Your company name, your product name, something distinctive. Put all your public API surface on that one object and leave nothing else on <code>window</code>.</p>
<h2>Rule 3: The DOM is not yours either</h2>
<p>You can't trust the DOM structure of the host page. Not all real-world HTML is valid. Pages exist without <code>&#x3C;head></code> elements. Certain CMS setups produce structures you wouldn't believe until you've seen them in production logs.</p>
<p>This is the kind of assumption that will eventually burn you:</p>
<pre><code class="language-js">document.head.appendChild(myScript);
</code></pre>
<p>If there's no <code>&#x3C;head></code>, this throws. And when a third-party script throws at top level, bad things happen to the page that included it.</p>
<p>A more defensive approach for injecting a script:</p>
<pre><code class="language-html">&#x3C;script>
(function() {
  var s = document.createElement('script');
  var g = document.getElementsByTagName('script')[0];
  s.src = 'your-script.js';
  s.type = 'text/javascript';
  g.parentNode.insertBefore(s, g);
}());
&#x3C;/script>
</code></pre>
<p>This relies on one assumption: the page contains at least one script tag — specifically, the one that ran this code. That's a safe assumption. It finds the first script element and inserts your new node before it. No <code>&#x3C;head></code> required.</p>
<p>The same logic applies everywhere. Check before you access. Handle the null case. Don't let missing DOM nodes throw unhandled errors into the host page's console — or worse, abort execution.</p>
<p>And beyond robustness: don't touch the DOM unless you need to. Every modification you make to someone else's page is a potential interference.</p>
<h2>Rule 4: Don't modify prototypes</h2>
<p><code>Object.prototype</code>, <code>String.prototype</code>, <code>Array.prototype</code> — these are shared. Every script on the page uses them. If you add a method to <code>String.prototype</code> because it's convenient, you've changed the behaviour of strings for every other script on that page. Including ones you've never heard of, built by people who had no idea you existed.</p>
<p>Even if your addition seems harmless, it creates the potential for naming collisions and unexpected behaviour in code you don't control. The rule is simple: don't do it. Utility functions belong in your local scope, not on global prototypes.</p>
<h2>Rule 5: Don't bundle big libraries</h2>
<p>Bundling jQuery — or any large library — into your third-party script creates two problems at once.</p>
<p>First, you're adding significant weight to the host page. If every third-party integration took this approach, the combined bundle size would be enormous. The people who integrate your script haven't agreed to that cost.</p>
<p>Second, and more insidiously: if the host page already uses jQuery, you now have two copies of jQuery on the same page. If they're different versions, the global <code>$</code> might end up pointing to yours, not theirs — and their code breaks. Even if you scope your library properly, the interaction between two copies of a large library is a source of subtle, hard-to-diagnose bugs.</p>
<p>Plain JavaScript solves both problems. Browser APIs have come a long way. Most of what you'd reach for a library to do, you can do without one. If you genuinely can't avoid a dependency, at minimum ensure it runs in a contained scope and doesn't leak globals.</p>
<hr>
<p>The thread running through all of this is the same as Rule 0: you're a guest. Your code has to work in environments you haven't tested, alongside scripts you've never seen, on pages that may not be well-formed. The measure of a good third-party integration isn't just that it works — it's that it doesn't break anything that was working before you arrived.</p>
]]></content:encoded>
            <author>sebastian@stoehr.dev (Sebastian Stöhr)</author>
            <enclosure url="https://www.sebastianstoehr.de/images/og/blog/en/third-party-javascript.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>