markdown and yaml files as a basic cms for react or vue development

The copy problem

Most websites and applications contain a large volume of text copy. Inevitably, various people within an organization beyond the initial implementers will want to change or update that text over time: writing styles change, explanations of products evolve, additional clarity is added to reduce complexity for users.

When the decision to update copy is made, it's a rather annoying task for engineers. It usually takes the form of searching the codebase for the snippet from the existing text, updating it, and then going through a code review and build process. While the task itself doesn't take much time, it's an interruption, and the work isn't the best use of an engineer's time. The standard code review process is also geared towards syntactic and architectural review of code, not the refinement of human-language writing.

Copy updates also happen all the time, and come from all over the place. Ownership of text within an engineering organization is notoriously diffuse, since it's straddles the boundary between of product, marketing, customer experience, legal, and management employees. Even with a strong product copywriter taking control of this process, it's unlikely that text updates will show up to engineering in an organized manner.

Approaches to extracting copy

Since we can't just stop copy updates, we might as well make them easier to make.

By separating out copy from the rest of an application, it's possible to at least centralize it in one place for easier updating and review by non-technical people. Generally, this approach falls into the category of Content Management Systems (CMS), of which there are many flavors that exist in the wild.

Blogging engines

Blogging tools backed by a database, like WordPress, Ghost, or Medium, are effectively content management systems geared towards creating articles. By providing an interface that non-technical users can access to create content that is later rendered into HTML templates, articles can be updated without involving an engineer.

While customizability is a central feature of most blogging engines, they tend to take a rather all-or-nothing approach to how content is presented: it's hard to use a database-backed blogging engine as a storage facility for smaller pieces of content, like form descriptions, that are littered throughout non-trivial applications. This is why you'll see many companies and projects where the application itself is run using a standard web server, and the "blog" part of the site will be a separate domain that runs a blogging system, having only some shared styles and configuration between the two.

Blog tools allow for easy editing, but render the whole blog

Adding a requirement on a database to look up text adds additional complexity to the development and build processes as well: any dependency on an external system will add additional overhead for developers. While some of the configuration challenge can be avoided by static site generators, which use files in source control as their source of content, I haven't seen any such approach that allows for hot reloading of content embedded within a larger site as it is written. This is particularly important when writing content for the often unpredictable medium of a responsive website, where issues of length and layout are paramount, and cutting down the feedback cycle is essential to implementing a high quality design.

Internationalization libraries

Beyond the benefit of increased visibilty due to centralization, extracting copy from within a website also allows for internationalization (i18n), the process by which an application is made to work in multiple languages.

I18n libraries have almost the opposite problem of blogging engines: they are too focused on updating small fragments of a website, and allow for little flexibility in formatting of content.

Optimizely and marketing tools

In recent years, marketing tools like Optimizely have emerged, which allow non-technical people to launch experiments with changing text on webpages, by including a snippet of code that will overwrite content in a user's page.

While these tools could in theory be used to update copy throughout a website in the long term, the fact that copy is only updated after a page loads and only on the client means that it can result in a worse user experience (since content may flash from a placeholder to the final copy), and will also not be correctly indexed by search engines. Their primary focus is also experimentation—the idea is that once an experiment is complete, an engineer would change underlying text to the most impactful variant.

Using markdown and yaml as a basic CMS

For simpler applications, full-blown CMS integrations don't have the right ROI. Blogging engines don't provide the flexibility in copy types that an application requires, and i18n libraries are overkill if your target customers are speakers of a single language.

Instead of using a database as a backend, I've long been interested in using structured file formats like JSON, YAML, or Markdown to store text. Markdown is a simple, plaintext format, but it allows for some basic styling, such as the creation of headings, lists, and tables in the HTML that it is compiled into, making it ideal for simple copywriting in a frontend application.

In a recent project, I used this approach in a React.js application, and here are some notes on the approach.

Loading markdown into a React.js or Vue application using webpack

Webpack is the most common way to manage module dependencies in modern frontend web applications, but it doesn't understand what to do with markdown or html files. Thankfully, the `html-loader` and `markdown-loader` modules exist on npm which allow for reading markdown files as HTML, which can then be rendered by the application. Once installed (see the documentation on markdown-loader), it's possible to import Markdown files and use the rendered HTML elsewhere:

import title = require('./strings/');
import description = require('./strings/');

// Within a react component...
render() {
  return (
    <h1 dangerouslySetInnerHTML={{ __html: title }} />
    <div dangerouslySetInnerHTML={{ __html: description }} />

Using directories to structure copy

It would be quite tedious to need to directly refer to a specific copy file for each text instance in the application. It's a better idea to centralize the importing of copy files in a centralized location, which can then be consumed throughout.

The `babel-plugin-import-glob` allows us to import files my pattern matching, which prevents the need to manually import each file individually. Using this, an object with all of our copy is simple to make:


// Unfortunately, recursive globbing doesn't work here,
//so we have to do this somewhat ugly hack.
import * as firstLevel from 'glob:./strings/**.md';
import * as secondLevel from 'glob:./strings/**/**.md';
import * as thirdLevel from 'glob:./strings/**/**/**.md';

import set from 'set-value'; // npm install -s set-value

// babel-plugin-import-glob separates path with this character
const buildStrings = () => {
  const allEntries = { ...firstLevel, ...secondLevel, ...thirdLevel };
  const nested = {};
  Object.keys(entries).forEach((key) => {
    const keyWithDots = key.replace(DIRECTORY_SEPARATOR, '.');
    set(nested, keyWithDots, entries[key]);
  return nested;

const strings = buildStrings();
export default strings;

Then, in our components, we can refer to children of this exported object rather than importing each piece of content directly.

import strings = require('./strings');

// Within a react component...
render() {
  return (
    <h1 dangerouslySetInnerHTML={{ __html: strings.title }} />
    <div dangerouslySetInnerHTML={{ __html: string.description }} />

In addition to centralization, what's great about this is that features like hot reloading work straight out of the box.

Possible extensions

Using yaml to store smaller copy snippets

It would be overkill to create a separate file that just contained the word "Next" to specify what should appear on a button. A similar approach can be taken to load yaml and expose strings that way for shorter pieces of text, perhaps in an `index.yaml` file that could appear in each directory.

Frontend components that indicate which piece of content they are backed by

In the approach above, to figure out what content to update, it would still be necessary to search the codebase for a particular piece of text. Another approach would be to create a component for including copy that is aware of what path it is referencing: then a debug mode or a special shortcut could be used to display the key path to more easily track down the specific piece of content.

Frontend for directly editing the underlying content.

It's pretty easy to train non-technical people to make commits to github using the online interface, but a more streamlined copy-editing interface that generated the appropriate commits on the backend would be an added bonus to make the process of updating copy even easier, particularly if not everyone should have direct access to a github codebase.

I might at some point make this into a modular tool that could be dropped into any codebase, but I hope that some of these ideas help you practically deploy and update copy within your application while reducing maintenance overhead.