Gerard O'Neill

It’s Okay to Copy-paste HTML

Oct 2, 2023

An image of Star Wars Stormtroopers positioned in a grid. The image is meant to symbolize duplication, due to the Stormtroopers all being identical.

There's a very common practice that I see in front-end web application development which I consider to be an anti-pattern. Often, developers will write code to generate HTML inside of a loop, simply to avoid copy-pasting HTML. To me, this indicates a fundamental misunderstanding of the fact that HTML's purpose is to represent data; It is not simply a way of instructing browsers to display things on a user's screen. Thus, there's no real benefit to replacing HTML with another data format (e.g. JSON), and doing so leads to potential issues down the road.

To better understand what I mean, let's first take a look at the following React code*:

const Navbar = () => ( <div> <a href="/" className="navbar-link"> Home </a> <a href="/pricing" className="navbar-link"> Pricing </a> <a href="/about" className="navbar-link"> About </a> <a href="/register" className="navbar-link"> Sign up </a> <a href="/login" className="navbar-link"> Log in </a> </div> )

Many developers would look at this code and immediately think, There is code duplication here! I must create an abstraction so that my code is DRY. This is a good instinct, but there are multiple abstraction options, and the wrong one(s) get chosen far too often. The most common mistake that I see is to stuff the data into an array and create a loop to generate the HTML. Something like this, for example:

const routes = [ { url: '/', title: 'Home' }, { url: '/pricing', title: 'Pricing' }, { url: '/about', title: 'About' }, { url: '/register', title: 'Sign up' }, { url: '/login', title: 'Log in' }, ] const Navbar = () => ( <div> {routes.map({ url, title }) => ( <a key={url} href={url} className="navbar-link"> {title} </a> )} </div> )

In my opinion, this abstraction hasn't really done much. You could make the argument that we're doing less copy-pasting, but how true is that, really? I would argue that all this code does is transform HTML into an array of JSON objects. With a clever regex, one could easily transform it back. Instead of copy-pasting `<a href="...">...</a>`, we are now copy-pasting `{url: '...', title: '....' }`.

But let's say you disagree and continue down this path. The code produces the same markup at the end of the day, so it doesn't matter much. That starts to fall apart, however, when it's time to A/B test the "Sign up" button. Your team wants to know if the conversion rate would increase if the button had a different style from the rest of the buttons. Alright, so let's add that logic in there:

const routes = [ { url: '/', title: 'Home' }, { url: '/pricing', title: 'Pricing' }, { url: '/about', title: 'About' }, { url: '/register', title: 'Sign up', className: isAB() ? 'signup-link' : 'navbar-link' }, { url: '/login', title: 'Log in' }, ] const Navbar = () => ( <div> {routes.map({ url, title, className = 'navbar-link' }) => ( <a href={url} className={className}> {title} </a> )} </div> )

This might not seem like the worst thing in the world to some people, but I am already in lots of pain. I have been here before and it isn't fun. Because someone higher up than you just attended a conference where they were convinced that modals were better for user registration forms, and that the login flow should be built the same way as well.

At this point, it's probably pretty obvious that it would be annoying to implement modal-opening functionality when all of the routes are in an array like this. The path of least resistance is to unroll the array and copy-paste HTML instead of JSON:

const Navbar = () => ( <div> <a href="/" className="navbar-link"> Home </a> <a href="/pricing" className="navbar-link"> Pricing </a> <a href="/about" className="navbar-link"> About </a> <a role="button" className={isAB() ? 'signup-link' : 'navbar-link'} onClick={openRegistrationModal} > Sign up </a> <a role="button" className='navbar-link' onClick={openLoginModal}> Log in </a> </div> )

We still have a class name duplication problem, however. So what's the best way to solve that? In my opinion, it's to create a React component to take care of any default attributes, while still allowing the calling code to make modifications as necessary:

const NavbarLink = ({ className = 'navbar-link', ...props }) => ( <a className={className} {...props} /> ) const Navbar = () => ( <div> <NavbarLink href="/">Home</NavbarLink> <NavbarLink href="/pricing">Pricing</NavbarLink> <NavbarLink href="/about">About</NavbarLink> <NavbarLink role="button" className={isAB() ? 'signup-link' : undefined} onClick={openRegistrationModal} > Sign Up </NavbarLink> <NavbarLink role="button" onClick={openLoginModal} > Log in </NavbarLink> </div> )

With this code, we definitely still have copy-pasting, but it's not an issue. The most important thing is that the code is encapsulated in such a way that we minimize the number of modifications necessary to change to all abstracted elements (the class name, tag type, etc.) while also allowing for flexibility in the future. To be clear, the above code snippets are overly simplified for brevity. In real applications, there would be a lot more to abstract away than just a class name, making such an abstraction more important.

I hope I've done enough to convince you that copy-pasting HTML is often okay. Once again, there's typically never a benefit to copy-pasting JSON or some other data format instead of HTML, and such code usually needs to be reversed at some point in order to handle edge cases. As software engineers, we need to think about how easy it is for someone—whether ourselves or another engineer—to build new features on top of our architecture, and code accordingly.

* JSX is technically not HTML, but their differences are irrelevant in this discussion.


Want to see more content like this? Consider subscribing to my newsletter!