(Updated: )
/ #javascript #vuejs #node 

Post Frequency: from prototype to production with Vue + Node

If you want to ship, use the tools you know. — A lot of people

Let’s apply that principle: I’m building Post Frequency/Accountable Blogging, I’m familiar with Vue and Node-based backends. Netlify makes frontend deployments trivial and I’ve settled on Dokku for databases/backend applications hosting.

Table of contents:

Table of Contents

See how I deploy my applications Deployment options: Netlify + Dokku on DigitalOcean vs now.sh, GitHub Pages, Heroku and AWS.

Context

Accountable Blogging was always going to be a single-ish page application, initially the backend was going to be Netlify lambdas… when I hit some issues with that I just went back to what I know, setting up Dokku to deploy some backend services.

Since my lambdas were already written, I went for micro (see Simple but not too simple: how using Zeit’s micro improves your Node applications) instead of Express. In this other post, I go into more detail about why I did this, in short: it’s lighter and requires less boilerplate/helper packages to get a single POST endpoint up and running.

Frontend

Vue + Vue CLI 3

I’m a big fan of Vue. It’s simple, it has good docs, it’s easy to write.

I had never really tried the Vue CLI. Just like the rest of the official Vue packages and ecosytem, it’s got super nice ergonomics (eg. a plugin system, a local UI, hot module reloading that works).

Vue CLI prerender SPA plugin

To have indexable HTML with our SPA, we can leverage vue-cli-plugin-prerender-spa:

# if you have vue-cli globally installed
vue add prerender-spa
# if you have it only locally installed
npx vue add prerender-spa

Load some scripts only on production

There are situations where you only want to load some scripts on production.

Here’s how to do it, with the example of the crisp.chat JavaScript SDK (it’s slow to load and doesn’t make sense to use locally) public/index.html (using EJS templating I believe):

<script type="text/javascript">
  window.$crisp=[];
</script>
<% if (NODE_ENV === 'production') { %>
    window.CRISP_WEBSITE_ID="SOME_ID";
    (function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();
  </script>
<% } %>

This <% if (NODE_ENV === 'production') { %> and the matching <% } %> exclude rendering the particular script in dev.

Load scripts only if not pre-rendering injectX using:

Sometimes you want to conditionally load things when not pre-rendering. This can be achieved using window.__PRERENDER_INJECTED (which is a variable set by pre-render-SPA).

It looks like this:

if (!window.__PRERENDER_INJECTED || window.__PRERENDER_INJECTED !== 'PRERENDERING'){
  // init stuff
}

You need the following in .prerender-spa.json:

{
  "customRendererConfig": {
    "inject": "PRERENDERING"
  }
}

The full example would therefore be:

<script type="text/javascript">
    window.$crisp = [];
    window.CRISP_WEBSITE_ID="SOME_ID";
  </script>
  <% if (NODE_ENV === 'production') { %>
  <script>
    if (!window.__PRERENDER_INJECTED || window.__PRERENDER_INJECTED !== 'PRERENDERING'){
      (function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();
    }
  </script>
<% } %>

Pass service URLs using .env.${ENVIRONMENT} files

You probably want to hit a local version of your backend services when developing and obviously the live one when in production, here’s how you do that with .env files .env.development:

VUE_APP_FEED_SERVICE_URL=http://localhost:1234

.env.production:

VUE_APP_FEED_SERVICE_URL=https://my-live-service.accoutableblogging.com

In your application code you can then access it under process.env.VUE_APP_FEED_SERVICE_URL for example:

const FEED_SERVICE_URL = process.env.VUE_APP_FEED_SERVICE_URL;
export const FEED_DATA_URL = `${FEED_SERVICE_URL}`;

Being social and Google-friendly

Be crawlable

As a good internet citizen, we should be readable without JavaScript enabled. Since we’re leveraging Vue/vue-cli/vue-router we should probably pre-render (see Vue CLI prerender SPA plugin section).

What would be even nicer would be a sitemap.xml that you can submit to Google Webmaster tools, I haven’t found a nice solution yet but if/when I do you can be sure I’ll share it so subscribe to my newsletter.

Favicons and manifests

Get your assets from realfavicongenerator.net.

The end of the process on there is a download of a zip folder and some tags that you can copy.

Unzip the favicons and dump them in the public folder and add the tags to the head of the public/index.html file.

Meta description, OpenGraph tags

Create your assets with realfavicongenerator.net/social or metatags.io.

Here’s a fun gotcha: resources (URLs) in og tags need to be an absolute URL.

Without router

ie. you have an app that is a single page 🙂, then just add your tags to public/index.html.

With vue-router

I used this article, and you should subscribe to the newsletter, for next week’s article if you want the breakdown of how I’ve set it up.

Backend service(s)

Have a src/services folder where anything backend will live, say you have a some-service service.

My process starts with a directory src/services/some-service that contains an Express or micro app (see this article about how I set up my micro app).

For each service, we’ll create a git remote to Dokku (or Heroku or whatever other git-based method you’re using):

git remote add dokku-service dokku@${DOKKU_INSTANCE_IP}:some-service`

Deploying to Dokku from the project root (where the top-level package.json is), can be done with:

git subtree push --prefix src/services/some-service dokku-service master

If you would like to deploy using npm run deploy:service, you can put the following into the top-level package.json:

{
  "scripts": {
    "deploy:service": "git subtree push --prefix src/services/some-service dokku-service master"
  }
}

Launch

My launch was moderately successful, it started with an IndieHackers post: Show IH: A GitHub-style graph for your blog and a dev.to post: GitHub-style contribution graph for your blog.

Those two combined got me some traffic (90 referrals from IndieHackers and 60 from dev.to), see:

accountableblogging.com 23/10 to 30/10 page views graph

The ProductHunt launch went relatively well as well, see Post Frequency on ProductHunt. It got onto the front page (I reckon Ryan Hoover had something to do with that 😉) and got 100+ upvotes. This is more of a “check out this thing I made”, rather than a full-blown product so it’s not bad. Around 250 people checked it out from that (direct referrals from ProductHunt). The thing with ProductHunt is that other tools read data from there and syndicate it so you also see a trickle of other traffic, see the following graph:

accountableblogging.com 29/10 to 23/11 page views graph

Outcomes

I’ve currently got 18 email signups, if you’re interested you can sign up too: accountableblogging.com.

I’ve created a little email + survey people can take using Typeform, you’ll receive it if you sign up.

Next steps are to build the thing out. It’s simple: give me your URL + payment details, I track how much you publish, you meet your goal or pay.

unsplash-logoNeONBRAND

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.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.