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.
See how I deploy my applications Deployment options: Netlify + Dokku on DigitalOcean vs now.sh, GitHub Pages, Heroku and AWS.
Table of contents:
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:
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:
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.