Post Frequency: from prototype to production with Vue + Node
Tags: javascript, node, vue, productivity
Published: 2018-11-26 08:40:29

---
title: Post Frequency: from prototype to production with Vue + Node
published: true
tags: JavaScript,node,vuejs, productivity
canonical_url: https://codewithhugo.com/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](https://accountableblogging.com), 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.

See how I deploy my applications [Deployment options: Netlify + Dokku on DigitalOcean vs now.sh, GitHub Pages, Heroku and AWS](https://dev.to/hugo__df/deployment-options-netlify--dokku-on-digitalocean-vs-nowsh-github-pages-heroku-and-aws-4cab).


Table of contents:

- [Context](#context)

- [Frontend](#frontend)
    - [Vue + Vue CLI 3](#vue--vue-cli-3)

    - [Vue CLI prerender SPA plugin](#vue-cli-prerender-spa-plugin)
    - [Load some scripts only on production](#load-some-scripts-only-on-production)
    - [Load scripts only if *not* pre-rendering `injectX` using:](#load-scripts-only-if-not-pre-rendering-injectx-using)
- [Pass service URLs using `.env.${ENVIRONMENT}` files](#pass-service-urls-using-envenvironment-files)


- [Being social and Google-friendly](#being-social-and-google-friendly)
    - [Be crawlable](#be-crawlable)
    - [Favicons and manifests](#favicons-and-manifests)
    - [Meta description, OpenGraph tags](#meta-description-opengraph-tags)
        - [Without router](#without-router)
        - [With vue-router](#with-vue-router)
- [Backend service(s)](#backend-services)
- [Launch](#launch)
- [Outcomes](#outcomes)


## 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](https://dev.to/hugo__df/simple-but-not-too-simple-how-using-zeits-micro-improves-your-node-applications-1kah)) instead of Express. In [this other post](https://dev.to/hugo__df/simple-but-not-too-simple-how-using-zeits-micro-improves-your-node-applications-1kah), 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](https://www.npmjs.com/package/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](https://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](https://buttondown.email/hugo).

### Favicons and manifests

Get your assets from [realfavicongenerator.net](https://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](https://realfavicongenerator.net/social/) or [metatags.io](https://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](https://alligator.io/vuejs/vue-router-modify-head/), and you should [subscribe to the newsletter](https://buttondown.email/hugo), 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](https://dev.to/hugo__df/simple-but-not-too-simple-how-using-zeits-micro-improves-your-node-applications-1kah)).

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](https://indiehackers.com) post: [Show IH: A GitHub-style graph for your blog](https://www.indiehackers.com/forum/show-ih-a-github-style-graph-for-your-blog-da74a24d42) and a [dev.to](https://dev.to) post: [GitHub-style contribution graph for your blog](https://dev.to/hugo__df/github-style-contribution-graph-for-your-blog-78f).

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](https://i.imgur.com/iqfFJWP.png)

The ProductHunt launch went relatively well as well, [see Post Frequency on ProductHunt](https://www.producthunt.com/posts/post-frequency). 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](https://i.imgur.com/AP27VWy.png)

## Outcomes

I’ve currently got 18 email signups, if you’re interested you can sign up too: [accountableblogging.com](https://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.

[<svg xmlns="http://www.w3.org/2000/svg" style="height:12px;width:auto;position:relative;vertical-align:middle;top:-1px;fill:white" viewbox="0 0 32 32"><title>unsplash-logo</title>
<path d="M20.8 18.1c0 2.7-2.2 4.8-4.8 4.8s-4.8-2.1-4.8-4.8c0-2.7 2.2-4.8 4.8-4.8 2.7.1 4.8 2.2 4.8 4.8zm11.2-7.4v14.9c0 2.3-1.9 4.3-4.3 4.3h-23.4c-2.4 0-4.3-1.9-4.3-4.3v-15c0-2.3 1.9-4.3 4.3-4.3h3.7l.8-2.3c.4-1.1 1.7-2 2.9-2h8.6c1.2 0 2.5.9 2.9 2l.8 2.4h3.7c2.4 0 4.3 1.9 4.3 4.3zm-8.6 7.5c0-4.1-3.3-7.5-7.5-7.5-4.1 0-7.5 3.4-7.5 7.5s3.3 7.5 7.5 7.5c4.2-.1 7.5-3.4 7.5-7.5z"></path></svg>NeONBRAND](https://unsplash.com/@neonbrand?utm_medium=referral&utm_campaign=photographer-credit&utm_content=creditBadge "Download free do whatever you want high-resolution photos from NeONBRAND")