(Updated: )
/ #alpinejs #eleventy #javascript 

Integrating Alpine.js + Pre/Server-rendered content

Alpine.js is a great choice for adding some interactive feature to a server rendered, static or pre-rendered site.

In the Alpine.js GitHub Issues, we’ve seen a lot of questions around how to deal with content that’s pre-rendered or server-rendered in Alpine.js in order to use Alpine.js as a progressive enhancement.

From a poll I ran recently, 95.2% of Alpine.js users want to use it with a server-rendered page, static site generators or HTML pages.

I (want to) use @Alpine_JS in projects that are:

Server-rendered -> Rails/Laravel/Django/Express/WordPress/insert CMS
Static site (with generator) -> Hugo/Hexo/Jekyll/11ty
HTML pages -> hand-written HTML
SPAs (single page apps) -> in React/Angular/Ember/Vue, or with a router

Poll results (21 votes)

  • Server rendered: 71.4%
  • Static sites (with generator): 9.5%
  • HTML pages: 14.3%
  • SPAs: 4.8%

— Hugo (@hugo__df) March 27, 2020

This post details solutions to handling these cases. You can find all the examples at alpinejs.codewithhugo.com/pre-rendered-content/.

Table of Contents

Hiding rendered HTML in Alpine’s x-show=“false” or x-show=“0”

Alpine.js has a directive for showing/hiding content: x-show. We can use it with an “always-falsy” expression (0, null, undefined, '', false, NaN).

For example, we can set x-show="false" (to be obvious) or x-show="0" (to be compact) on any content that’s pre-rendered or server-rendered in order to hide it.

Note: we’re hiding elements (ie. style="display: none"), therefore they still exist on the page, so it’s not suitable for things like options inside of a “select” (as the last example shows).

<div
  x-data="{ options: ['hello', 'world'], selected: 'world' }"
>
  <p>
    <span x-show="true">pre-rendered content that won't be hidden</span>
  </p>
  <p>
    the content in "<span x-show="false">pre-rendered to be hidden</span
    >" will get hidden
  </p>
  <p>
    the content in "<span x-show="0">pre-rendered to be hidden</span>"
    will get hidden
  </p>
  <p>
    Example with a select that doesn't quite work, we end up with 2
    "hello" options, one is selected & hidden and the other is visible
    and unselected:
  </p>
  <select x-model="selected">
    <template x-for="o in options" :key="o">
      <option :value="o" x-text="o" :selected="o === selected"></option>
    </template>
    <option x-show="false" value="hello" selected>hello</option>
  </select>
</div>

You can see this example at work at Alpine.js Playground - example using x-show with pre-rendered content.

We’ve now seen how to use x-show to hide server-rendered or pre-rendered content on your page.

Next we’ll see how to overwrite some content using x-text/x-html.

Overwriting text/HTML content using x-text/x-html

The next situation one might encounter is that some text is pre-populated on the server but should be driven by Alpine.js once it boots up.

I dealt with this situation in Integrating Alpine.js with Eleventy & YAML files to create Alpine Playground’s Collections.

The way to deal with it is to wrap relevant content in a span (or other element) that will have the x-text or x-html property set. As in the following example.

Another thing we could do is store the initial HTML/text into a variable somewhere, that would be done using x-refs and $refs/this.$refs in x-init. Let me know on Twitter or the Alpine.js Discord if that’s something you need more guidance on.

<div
  x-data="{ text: 'alpine-render', html: '<button>btn from alpine</button>' }"
  x-init="setTimeout(() => {
    text = 'alpine-render-2';
    html = '<button>alpine-render-2</button>';
  }, 1000);
  "
>
  <span x-text="text">pre-rendered text</span>
  <div x-html="html">
    <h3>Pre-rendered HTML</h3>
  </div>
</div>

You can see this example at work at Alpine.js Playground - example overriding pre-render HTML or text with x-html/x-text.

We’ve now seen how to overwrite text and HTML content with x-text and x-html.

Next we’ll see how to remove DOM Nodes using x-ref and x-init.

Removing single nodes using x-ref + x-init

If we got back to the “select” situation in the first section. If we server/pre-render some options, we actually need to delete them.

In order to do this we can use x-ref and then use $refs/this.$refs in x-init. Refs give us access to a DOM Node inside the Alpine.js Component. Once we’ve got the DOM Node, we can call .remove() on the node. For example if we’ve got <span x-ref="remove1">Hello</span> we can call $refs.remove1.remove() in x-init to remove the whole Node.

<div
  x-data="{ options: ['hello', 'world'], selected: 'world' }"
  x-init="
    $refs.remove1.remove();
    $refs.remove2.remove();
    $refs.remove3.remove();
  "
>
  <p>
    The following text will get removed with x-ref approach "<span
      x-ref="remove1"
      >pre-rendered content</span
    >"
  </p>
  <p>
    As will the following text: "<span x-ref="remove2"
      >pre-rendered content</span
    >"
  </p>
  <p>
    A more sensible example with the "select" that works... mostly
    ("world" should be selected)
  </p>
  <select x-model="selected">
    <template x-for="o in options" :key="o">
      <option :value="o" x-text="o" :selected="o === selected"></option>
    </template>
    <option x-ref="remove3" value="hello" selected>hello</option>
  </select>
</div>

You can see this example at work at Alpine.js Playground - example removing pre-rendered nodes using x-refs and x-init.

We’ve now seen how to use x-ref and $refs.refName.remove() to delete nodes. However, keeping track of multiple refs is quite verbose.

In the next section we’ll look at how to introduce an x-remove attribute to mark Nodes to be deleted on x-init.

Removing nodes using x-remove + x-init code

x-remove is not an attribute that Alpine.js does anything with, we’ll implement our own functionality around it though.

In the previous section we used x-ref and $refs to find which DOM Nodes to call .remove() on. That was tedious, for each Node to delete, we need a new ref and a new remove() statement.

Instead we can mark elements to be deleted with the x-remove attribute. Then in x-init, we can use $el.querySelectorAll('[x-remove]') (or this.$el.querySelectorAll if defining x-init in JavaScript) to find all the Nodes that have this attribute.

Once we’ve got all the nodes, we can loop over them and call .remove() on each of them.

In code that’s: $el.querySelectorAll('[x-remove]').forEach(e => e.remove()).

Note: deleting options causes issues with x-model on the select, see the next section

<div
  x-data="{ options: ['hello', 'world'], selected: 'world' }"
  x-init="
    $el.querySelectorAll('[x-remove]')
      .forEach(e => e.remove());
  "
>
  <p>
    The following text will get removed with x-ref approach "<span
      x-remove
      >pre-rendered content</span
    >"
  </p>
  <p>
    As will the following text: "<span x-remove
      >pre-rendered content</span
    >"
  </p>
  <p>
    A more sensible example with the "select" that works... mostly
    ("world" should be selected)
  </p>
  <select x-model="selected">
    <template x-for="o in options" :key="o">
      <option :value="o" x-text="o" :selected="o === selected"></option>
    </template>
    <option x-remove value="hello" selected>hello</option>
  </select>
</div>

You can see this example at work at Alpine.js Playground - example removing pre-rendered nodes using x-remove attribute and x-init.

We’ve now seen how to use x-remove, x-init and $el to have a cleaner way to remove pre-rendered DOM Nodes.

That’s it for this post, you can check out Integrating Alpine.js with Eleventy & YAML files to create Alpine Playground’s Collections for more learnings working with Alpine.js + server/pre-rendered content.

If you’re interested in Alpine.js, Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles.

unsplash-logoTekton

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Interested in Alpine.js?

Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles