Let’s Make a QR Code Generator With a Serverless Function!

QR codes are funny, right? We love them, then hate them, then love them again. Anyways, they’ve lately been popping up again and it got me thinking about how they’re made. There are like a gazillion QR code generators out there, but say it’s something you need to do on your own website. This package can do that. But it’s also weighs in at a hefty 180 KB for everything it needs to generate stuff. You wouldn’t want to serve all that along with the rest of your scripts.

Now, I’m relatively new to the concept of cloud functions, but I hear that’s the bee’s knees for something just like this. That way, the function lives somewhere on a server that can be called when it’s needed. Sorta like a little API to run the function.

Some hosts offer some sort of cloud function feature. DigitalOcean happens to be one of them! And, like Droplets, functions are pretty easy to deploy.

Create a functions folder locally

DigitalOcean has a CLI that with a command that’ll scaffold things for us, so cd wherever you want to set things up and run:

doctl serverless init --language js qr-generator

Notice the language is explicitly declared. DigitalOcean functions also support PHP and Python.

We get a nice clean project called qr-generator with a /packages folder that holds all the project’s functions. There’s a sample function in there, but we can overlook it for now and create a qr folder right next to it:

That folder is where both the qrcode package and our qr.js function are going to live. So, let’s cd into packages/sample/qr and install the package:

npm install --save qrcode

Now we can write the function in a new qr.js file:

const qrcode = require('qrcode')

exports.main = (args) => {
  return qrcode.toDataURL(args.text).then(res => ({
    headers:  { 'content-type': 'text/html; charset=UTF-8' },
    body: args.img == undefined ? res : `<img src="${res}">`
  }))
}

if (process.env.TEST) exports.main({text:"hello"}).then(console.log)

All that’s doing is requiring the the qrcode package and exporting a function that basically generates an <img> tag with the a base64 PNG for the source. We can even test it out in the terminal:

doctl serverless functions invoke sample/qr -p "text:css-tricks.com"

Check the config file

There is one extra step we need here. When the project was scaffolded, we got this little project.yml file and it configures the function with some information about it. This is what’s in there by default:

targetNamespace: ''
parameters: {}
packages:
  - name: sample
    environment: {}
    parameters: {}
    annotations: {}
    actions:
      - name: hello
        binary: false
        main: ''
        runtime: 'nodejs:default'
        web: true
        parameters: {}
        environment: {}
        annotations: {}
        limits: {}

See those highlighted lines? The packages: name property is where in the packages folder the function lives, which is a folder called sample in this case. The actions/ name property is the name of the function itself, which is the name of the file. It’s hello by default when we spin up the project, but we named ours qr.js, so we oughta change that line from hello to qr before moving on.

Deploy the function

We can do it straight from the command line! First, we connect to the DigitalOcean sandbox environment so we have a live URL for testing:

## You will need an DO API key handy
doctl sandbox connect

Now we can deploy the function:

doctl sandbox deploy qr-generator

Once deployed, we can access the function at a URL. What’s the URL? There’s a command for that:

doctl sbx fn get sample/qr --url
https://faas-nyc1-2ef2e6cc.doserverless.co/api/v1/web/fn-10a937cb-1f12-427b-aadd-f43d0b08d64a/sample/qr

Heck yeah! No more need to ship that entire package with the rest of the scripts! We can hit that URL and generate the QR code from there.

Demo

We fetch the API and that’s really all there is to it!


Let’s Make a QR Code Generator With a Serverless Function! originally published on CSS-Tricks. You should get the newsletter.

Creating the DigitalOcean Logo in 3D With CSS

Howdy y’all! Unless you’ve been living under a rock (and maybe even then), you’ve undoubtedly heard the news that CSS-Tricks, was acquired by DigitalOcean. Congratulations to everyone! 🥳

As a little hurrah to commemorate the occasion, I wanted to create the DigitalOcean logo in CSS. I did that, but then took it a little further with some 3D and parallax. This also makes for quite a good article because the way I made the logo uses various pieces from previous articles I’ve written. This cool little demo brings many of those concepts together.

So, let’s dive right in!

Creating the DigitalOcean logo

We are going to “trace” the DigitalOcean logo by grabbing an SVG version of it from simpleicons.org.

<svg role="img" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <title>DigitalOcean</title>
  <path d="M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z"></path>
</svg>

Being mindful that we’re taking this 3D, we can wrap our SVG in a .scene element. Then we can use the tracing technique from my “Advice for Advanced CSS Illustrations” article. We are using Pug so we can leverage its mixins and reduce the amount of markup we need to write for the 3D part.

- const SIZE = 40
.scene
  svg(role='img' viewbox='0 0 24 24' xmlns='http://www.w3.org/2000/svg')
    title DigitalOcean
    path(d='M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z')
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
    .logo__square.logo__square--two
    .logo__square.logo__square--three

The idea is to style these elements so that they overlap our logo. We don’t need to create the “arc” portion of the logo as we’re thinking ahead because we are going to make this logo in 3D and can create the arc with two cylinder shapes. That means for now all we need is the containing elements for each cylinder, the inner arc, and the outer arc.

Check out this demo that lays out the different pieces of the DigitalOcean logo. If you toggle the “Explode” and hover elements, you can what the logo consists of.

If we wanted a flat DigitalOcean logo, we could use a CSS mask with a conic gradient. Then we would only need one “arc” element that uses a solid border.

.logo__arc--outer {
  border: calc(var(--size) * 0.1925vmin) solid #006aff;
  mask: conic-gradient(transparent 0deg 90deg, #000 90deg);
  transform: translate(-50%, -50%) rotate(180deg);
}

That would give us the logo. The “reveal” transitions a clip-path that shows the traced SVG image underneath.

Check out my “Advice for Complex CSS Illustrations” article for tips on working with advanced illustrations in CSS.

Extruding for the 3D

We have the blueprint for our DigitalOcean logo, so it’s time to make this 3D. Why didn’t we create 3D blocks from the start? Creating containing elements, makes it easier to create 3D via extrusion.

We covered creating 3D scenes in CSS in my “Learning to Think in Cubes Instead of Boxes” article. We are going to use some of those techniques for what we’re making here. Let’s start with the squares in the logo. Each square is a cuboid. And using Pug, we are going to create and use a cuboid mixin to help generate all of them.

mixin cuboid()
  .cuboid(class!=attributes.class)
    if block
      block
    - let s = 0
    while s < 6
      .cuboid__side
      - s++

Then we can use this in our markup:

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
    .logo__arc.logo__arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three

Next, we need the styles to display our cuboids. Note that cuboids have six sides, so we’re styling those with the nth-of-type() pseudo selector while leveraging the vmin length unit to keep things responsive.

.cuboid {
  width: 100%;
  height: 100%;
  position: relative;
}
.cuboid__side {
  filter: brightness(var(--b, 1));
  position: absolute;
}
.cuboid__side:nth-of-type(1) {
  --b: 1.1;
  height: calc(var(--depth, 20) * 1vmin);
  width: 100%;
  top: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(2) {
  --b: 0.9;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  top: 50%;
  right: 0;
  transform: translate(50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(3) {
  --b: 0.5;
  width: 100%;
  height: calc(var(--depth, 20) * 1vmin);
  bottom: 0;
  transform: translate(0%, 50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(4) {
  --b: 1;
  height: 100%;
  width: calc(var(--depth, 20) * 1vmin);
  left: 0;
  top: 50%;
  transform: translate(-50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(5) {
  --b: 0.8;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * 0.5vmin));
  top: 0;
  left: 0;
}
.cuboid__side:nth-of-type(6) {
  --b: 1.2;
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--depth, 20) * -0.5vmin)) rotateY(180deg);
  top: 0;
  left: 0;
}

We are approaching this in a different way from how we have done it in past articles. Instead of applying height, width, and depth to a cuboid, we are only concerned with its depth. And instead of trying to color each side, we can make use of filter: brightness to handle that for us.

If you need to have cuboids or other 3D elements as a child of a side using filter, you may need to shuffle things. A filtered side will flatten any 3D children.

The DigitalOcean logo has three cuboids, so we have a class for each one and are styling them like this:

.square-cuboid .cuboid__side {
  background: hsl(var(--hue), 100%, 50%);
}
.square-cuboid--one {
  /* 0.1925? It's a percentage of the --size for that square */
  --depth: calc((var(--size) * 0.1925) * var(--depth-multiplier));
}
.square-cuboid--two {
  --depth: calc((var(--size) * 0.1475) * var(--depth-multiplier));
}
.square-cuboid--three {
  --depth: calc((var(--size) * 0.125) * var(--depth-multiplier));
}

…which gives us something like this:

You can play with the depth slider to extrude the cuboids as you wish! For our demo, we’ve chosen to make the cuboids true cubes with equal height, width, and depth. The depth of the arc will match the largest cuboid.

Now for the cylinders. The idea is to create two ends that use border-radius: 50%. Then, we can use many elements as the sides of the cylinder to create the effect. The trick is positioning all the sides.

There are various approaches we can take to create the cylinders in CSS. But, for me, if this is something I can foresee using many times, I’ll try and future-proof it. That means making a mixin and some styles I can reuse for other demos. And those styles should try and cater to scenarios I could see popping up. For a cylinder, there is some configuration we may want to consider:

  • radius
  • sides
  • how many of those sides are displayed
  • whether to show one or both ends of the cylinder

Putting that together, we can create a Pug mixin that caters to those needs:

mixin cylinder(radius = 10, sides = 10, cut = [5, 10], top = true, bottom = true)
  - const innerAngle = (((sides - 2) * 180) / sides) * 0.5
  - const cosAngle = Math.cos(innerAngle * (Math.PI / 180))
  - const side =  2 * radius * Math.cos(innerAngle * (Math.PI / 180))
  //- Use the cut to determine how many sides get rendered and from what point
  .cylinder(style=`--side: ${side}; --sides: ${sides}; --radius: ${radius};` class!=attributes.class)
    if top
      .cylinder__end.cylinder__segment.cylinder__end--top
    if bottom
      .cylinder__end.cylinder__segment.cylinder__end--bottom
    - const [start, end] = cut
    - let i = start
    while i < end
      .cylinder__side.cylinder__segment(style=`--index: ${i};`)
      - i++

See how //- is prepended to the comment in the code? That tells Pug to ignore the comment and leave it out from the compiled HTML markup.

Why do we need to pass the radius into the cylinder? Well, unfortunately, we can’t quite handle trigonometry with CSS calc() just yet (but it is coming). And we need to work out things like the width of the cylinder sides and how far out from the center they should project. The great thing is that we have a nice way to pass that information to our styles via inline custom properties.

.cylinder(
  style=`
    --side: ${side};
    --sides: ${sides};
    --radius: ${radius};`
  class!=attributes.class
)

An example use for our mixin would be as follows:

+cylinder(20, 30, [10, 30])

This would create a cylinder with a radius of 20, 30 sides, where only sides 10 to 30 are rendered.

Then we need some styling. Styling the cylinders for the DigitalOcean logo is pretty straightforward, thankfully:

.cylinder {
  --bg: hsl(var(--hue), 100%, 50%);
  background: rgba(255,43,0,0.5);
  height: 100%;
  width: 100%;
  position: relative;
}
.cylinder__segment {
  filter: brightness(var(--b, 1));
  background: var(--bg, #e61919);
  position: absolute;
  top: 50%;
  left: 50%;
}
.cylinder__end {
  --b: 1.2;
  --end-coefficient: 0.5;
  height: 100%;
  width: 100%;
  border-radius: 50%;
  transform: translate3d(-50%, -50%, calc((var(--depth, 0) * var(--end-coefficient)) * 1vmin));
}
.cylinder__end--bottom {
  --b: 0.8;
  --end-coefficient: -0.5;
}
.cylinder__side {
  --b: 0.9;
  height: calc(var(--depth, 30) * 1vmin);
  width: calc(var(--side) * 1vmin);
  transform: translate(-50%, -50%) rotateX(90deg) rotateY(calc((var(--index, 0) * 360 / var(--sides)) * 1deg)) translate3d(50%, 0, calc(var(--radius) * 1vmin));
}

The idea is that we create all the sides of the cylinder and put them in the middle of the cylinder. Then we rotate them on the Y-axis and project them out by roughly the distance of the radius.

There’s no need to show the ends of the cylinder in the inner part since they’re already obscured. But we do need to show them for the outer portion. Our two-cylinder mixin use look like this:

.logo(style=`--size: ${SIZE}`)
  .logo__arc.logo__arc--inner
    +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
  .logo__arc.logo__arc--outer
    +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer

We know the radius from the diameter we used when tracing the logo earlier. Plus, we can use the outer cylinder ends to create the faces of the DigitalOcean logo. A combination of border-width and clip-path comes in handy here.

.cylinder-arc--outer .cylinder__end--top,
.cylinder-arc--outer .cylinder__end--bottom {
  /* Based on the percentage of the size needed to cap the arc */
  border-width: calc(var(--size) * 0.1975vmin);
  border-style: solid;
  border-color: hsl(var(--hue), 100%, 50%);
  --clip: polygon(50% 0, 50% 50%, 0 50%, 0 100%, 100% 100%, 100% 0);
  clip-path: var(--clip);
}

We’re pretty close to where we want to be!

There is one thing missing though: capping the arc. We need to create some ends for the arc, which requires two elements that we can position and rotate on the X or Y-axis:

.scene
  .logo(style=`--size: ${SIZE}`)
    .logo__arc.logo__arc--inner
      +cylinder((SIZE * 0.61) * 0.5, 80, [0, 60], false, false).cylinder-arc.cylinder-arc--inner
    .logo__arc.logo__arc--outer
      +cylinder((SIZE * 1) * 0.5, 100, [0, 75], true, true).cylinder-arc.cylinder-arc--outer
    .logo__square.logo__square--one
      +cuboid().square-cuboid.square-cuboid--one
    .logo__square.logo__square--two
      +cuboid().square-cuboid.square-cuboid--two
    .logo__square.logo__square--three
      +cuboid().square-cuboid.square-cuboid--three
    .logo__cap.logo__cap--top
    .logo__cap.logo__cap--bottom

The arc’s capped ends will assume the height and width based on the end’s border-width value as well as the depth of the arc.

.logo__cap {
  --hue: 10;
  position: absolute;
  height: calc(var(--size) * 0.1925vmin);
  width: calc(var(--size) * 0.1975vmin);
  background: hsl(var(--hue), 100%, 50%);
}
.logo__cap--top {
  top: 50%;
  left: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.logo__cap--bottom {
  bottom: 0;
  right: 50%;
  transform: translate(50%, 0) rotateY(90deg);
  height: calc(var(--size) * 0.1975vmin);
  width: calc(var(--size) * 0.1925vmin);
}

We’ve capped the arc!

Throwing everything together, we have our DigitalOcean logo. This demo allows you to rotate it in different directions.

But there’s still one more trick up our sleeve!

Adding a parallax effect to the logo

We’ve got our 3D DigitalOcean logo but it would be neat if it was interactive in some way. Back in November 2021, we covered how to create a parallax effect with CSS custom properties. Let’s use that same technique here, the idea being that the logo rotates and moves by following a user’s mouse cursor.

We do need a dash of JavaScript so that we can update the custom properties we need for a coefficient that sets the logo’s movement along the X and Y-axes in the CSS. Those coefficients are calculated from a user’s pointer position. I’ll often use GreenSock so I can use gsap.utils.mapRange. But, here is a vanilla JavaScript version of it that implements mapRange:

const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => {
  const INPUT_RANGE = inputUpper - inputLower
  const OUTPUT_RANGE = outputUpper - outputLower
  return value => outputLower + (((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0)
}

const BOUNDS = 100      
const update = ({ x, y }) => {
  const POS_X = mapRange(0, window.innerWidth, -BOUNDS, BOUNDS)(x)
  const POS_Y = mapRange(0, window.innerHeight, -BOUNDS, BOUNDS)(y)
  document.body.style.setProperty('--coefficient-x', POS_X)
  document.body.style.setProperty('--coefficient-y', POS_Y)
}

document.addEventListener('pointermove', update)

The magic happens in CSS-land. This is one of the major benefits of using custom properties this way. JavaScript is telling CSS what’s happening with the interaction. But, it doesn’t care what CSS does with it. That’s a rad decoupling. I use this JavaScript snippet in so many of my demos for this very reason. We can create different experiences simply by updating the CSS.

How do we do that? Use calc() and custom properties that are scoped directly to the .scene element. Consider these updated styles for .scene:

.scene {
  --rotation-y: 75deg;
  --rotation-x: -14deg;
  transform: translate3d(0, 0, 100vmin)
    rotateX(-16deg)
    rotateY(28deg)
    rotateX(calc(var(--coefficient-y, 0) * var(--rotation-x, 0deg)))
    rotateY(calc(var(--coefficient-x, 0) * var(--rotation-y, 0deg)));
}

The makes the scene rotate on the X and Y-axes based on the user’s pointer movement. But we can adjust this behavior by tweaking the values for --rotation-x and --rotation-y.

Each cuboid will move its own way. They are able to move on either the X, Y, or Z-axis. But, we only need to define one transform. Then we can use scoped custom properties to do the rest.

.logo__square {
  transform: translate3d(
    calc(min(0, var(--coefficient-x, 0) * var(--offset-x, 0)) * 1%),
    calc((var(--coefficient-y) * var(--offset-y, 0)) * 1%),
    calc((var(--coefficient-x) * var(--offset-z, 0)) * 1vmin)
  );
}
.logo__square--one {
  --offset-x: 50;
  --offset-y: 10;
  --offset-z: -2;
}
.logo__square--two {
  --offset-x: -35;
  --offset-y: -20;
  --offset-z: 4;
}
.logo__square--three {
  --offset-x: 25;
  --offset-y: 30;
  --offset-z: -6;
}

That will give you something like this:

And we can tweak these to our heart’s content until we get something we’re happy with!

Adding an intro animation to the mix

OK, I fibbed a bit and have one final (I promise!) way we can enhance our work. What if we had some sort of intro animation? How about a wave or something that washes across and reveals the logo?

We could do this with the pseudo-elements of the body element:

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
}

body:after,
body:before {
  content: '';
  position: absolute;
  height: 100vh;
  width: 100vw;
  background: hsl(var(--hue), 100%, calc(var(--lightness, 50) * 1%));
  transform: translate(100%, 0);
  animation-name: wave;
  animation-duration: calc(var(--wave-speed) * 1s);
  animation-delay: calc(var(--initial-delay) * 1s);
  animation-timing-function: ease-in;
}
body:before {
  --lightness: 85;
  animation-timing-function: ease-out;
}
@keyframes wave {
  from {
    transform: translate(-100%, 0);
  }
}

Now, the idea is that the DigitalOcean logo is hidden until the wave washes over the top of it. For this effect, we’re going to animate our 3D elements from an opacity of 0. And we’re going to animate all the sides to our 3D elements from a brightness of 1 to reveal the logo. Because the wave color matches that of the logo, we won’t see it fade in. Also, using animation-fill-mode: both means that our elements will extend the styling of our keyframes in both directions.

This requires some form of animation timeline. And this is where custom properties come into play. We can use the duration of our animations to calculate the delays of others. We looked at this in my “How to Make a Pure CSS 3D Package Toggle” and “Animated Matryoshka Dolls in CSS” articles.

:root {
  --hue: 215;
  --initial-delay: 1;
  --wave-speed: 2;
  --fade-speed: 0.5;
  --filter-speed: 1;
}

.cylinder__segment,
.cuboid__side,
.logo__cap {
  animation-name: fade-in, filter-in;
  animation-duration: calc(var(--fade-speed) * 1s),
    calc(var(--filter-speed) * 1s);
  animation-delay: calc((var(--initial-delay) + var(--wave-speed)) * 0.75s),
    calc((var(--initial-delay) + var(--wave-speed)) * 1.15s);
  animation-fill-mode: both;
}

@keyframes filter-in {
  from {
    filter: brightness(1);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

How do we get the timing right? A little tinkering and making use of the “Animations Inspector” in Chrome’s DevTool goes a long ways. Try adjusting the timings in this demo:

You may find that the fade timing is unnecessary if you want the logo to be there once the wave has passed. In that case, try setting the fade to 0. And in particular, experiment with the filter and fade coefficients. They relate to the 0.75s and 1.15s from the code above. It’s worth adjusting things and having a play in Chrome’s Animation Inspector to see how things time in.

That’s it!

Putting it all together, we have this neat intro for our 3D DigitalOcean logo!

And, of course, this only one approach to create the DigitalOcean logo in 3D with CSS. If you see other possibilities or perhaps something that can be optimized further, drop a link to your demo in the comments!

Congratulations, again, to the CSS-Tricks team and DigitalOcean for their new partnership. I’m excited to see where things go with the acquisition. One thing is for sure: CSS-Tricks will continue to inspire and produce fantastic content for the community. 😎


Creating the DigitalOcean Logo in 3D With CSS originally published on CSS-Tricks. You should get the newsletter.

App Platform on Digital Ocean

This is new stuff from DO.

App Platform is a hosting product, no surprise there, but it has some features that are Jamstack-inspired in the best possible way, and an additional set of unique and powerful features. Let’s start with some basics:

  • Static sites can be hosted on the free tier
  • Automatic HTTPS
  • Global CDN (Cloudflare is in front, so you’re DDoS safe)
  • Deploy from Git

That’s the stuff that developers like me are loving these days. Take some of the hardest, toil-laden, no-fun aspects of web development and entirely do them for me.

And now the drumroll:

  • This isn’t just for static sites: it’s for PHP, Node, Python, Ruby, Go, Docker Containers, etc.
  • You don’t have to configure and update things, these are boxes ready-to-go for those technologies.
  • You can scale to whatever you need.
  • You don’t pay by the team seat. Unlimited team members. You pay by usage like bandwidth and build time.

Use that link to get $100 in credit over 60 days.

It extremely easy to deploy a static site

You snag it right from GitHub (or GitLab, or Docker Hub), which is great right away, and off you go.

Then we get our first little hint of something compelling right away:

But let’s say we don’t need that immediately, we can go with a free plan and get this out.

The site will build and you can see logs:

And lookie that my static site is LIVE!

Say my site needs to run an actual build process? That, and lots more configuration come in the form of an “App Spec”. This is where I would include those build commands, change Git information, deployment zones, and loads more.

About that database…

Wasn’t that interesting to see the setup steps for this static site suggest adding a database? So many sites need some kind of data store, and it’s often left up to developers to go find some kind of cloud-accessible data storage that will work well with their app. With Digital Ocean App Platform, it can live right alongside your static app.

It’s called a component.

As you can see, it can be, but doesn’t have to be a Database. It could be another type of server! Here I could pop a PostgreSQL DB on there for just $7/month.

If what you need to add is an internal or external service, it will let you add that via another Git repo that you hook up. Oh my what a modern system you now have. A front end and a back end each individually deployable directly via Git itself.

This is for server-side apps as well.

This feels big to me! I get that same kinda easy DX feeling I get with static sites, but with, say, a Python or Ruby on Rails app. Free deployment! Server boxes I don’t have to configure and manage myself!

Seems like a pretty happy-path hosting environment for lots of stuff.

Use that link to get $100 in credit over 60 days.


The post App Platform on Digital Ocean appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Our Managed WordPress Hosting Test Results Are In…

Earlier this week we posted a detailed breakdown on how we’ve been performance testing WPMU DEV managed WordPress hosting against our primary competition.

In this post we’re going to share with you exactly how each host did.

And usually, whoever does these comparisons, wins them, right?

Well, not this time (ooooo!)……….

Here’s a quick recap of the hosting testing methodology we used, that you can replicate, for free, at home.

Basically, we…

  1. Took our top 8 hosting competitors based off general popularity and our members hosting usage, and tested the performance of their base managed WordPress plan versus ours, specifically: GoDaddy, Flywheel, WP Engine, Cloudways, SiteGround, BlueHost, Kinsta, and HostGator.
  2. Made an account with each host at their entry level (base) managed WordPress plan (apart from Cloudways as they don’t do managed WP) and created the same exact test website on each platform.
  3. Ran each host through a rigorous load test (to see how many users they can handle at the same time) using the awesome and freely available Loader.io – you can go run your own tests right now to see how you do.
  4. Put each hosts speed and Time To First Byte (TTFB) to the test with the equally free KeyCDN’s performance testing tool – again, go check it out and test your own host.
  5. Established how many parallel clients (read: users visiting the site at the same time) each host could take.
  6. Worked out TTFB in what we think is the fairest way (as they can vary dramatically based on server location): TTFB Average (Geo-Optimized), TTFB Average (All Locations).
  7. Did all this without implementing caching of CDNs, so you get to test the actual server in real dynamic conditions (much more on that decision in our methodology post, tl;dr you can put any host behind a great CDN and serve static pages like a gun, but WP isn’t about that… although we are open to adding that as a test too.)

Alright, now you’re all caught up, let’s not delay any further.

Dev Man is shocked at what he sees from these test results.
Dev Man might be in for a surprise with these results.

Here’s how our base plan fared against some of the most popular managed WordPress hosting providers on the web:

The raw results:

A look at the results of our WordPress hosting tests
*As of September 2020. Based on starting plans for each platform.

How each host ranked in each category:

Max Parallel Clients (how many users the host can handle at once)

1.Kinsta – 170
2.WPMU DEV – 140
3.Cloudways – 70
4.WP Engine – 50
4.Flywheel – 50
4.SiteGround – 50
5.Bluehost – 40
5.GoDaddy – 40
5.HostGator – 40

TTFB Average (speed of server response averaged across the globe)

1.GoDaddy – 332ms
2.Cloudways – 402ms
3.WPMU DEV – 476ms
4.WP Engine – 511ms
5.Kinsta – 622ms
6.SiteGround – 683ms
7.HostGator – 912ms
8.Bluehost – 1.5s
9.Flywheel – 1.7s

TTFB Best (the fastest response recorded, we assume this is down to geolocation)

1.Kinsta – 35.15ms
2.Cloudways – 53.34ms
3.GoDaddy – 66.5ms
4.WPMU DEV – 81.14ms
5.WP Engine – 170.23ms
6.SiteGround – 190.09ms
7.HostGator – 520.68ms
8.Bluehost – 1.2s
9.Flywheel – 1.35s

A quick summary of the results…

When it came to the maximum number of parallel clients each server handled during the load test, Kinsta came out on top with 170 concurrent users – followed closely by us with 140.

As we touched on in our methodology post, these hosts are the ones (metaphorically) letting the most people into the bar at the same time thanks to their higher parallel client numbers.

So that’s great work by Kinsta, being able to cope with that many users visiting your site on your base plan is pretty impressive, although we’re pretty chuffed about our second place.

In terms of speed, Kinsta also took out the TTFB (Geo-Optimized) category with the speediest TTFB time (35.15ms) of them all… we’re betting that KeyCDN and their servers are not all that far apart.

And lastly, the TTFB Average (All Locations) crown went to GoDaddy, with an average TTFB time of 332ms over the 10 locations that KeyCDN accounted for. Nice work to the big GD!

We came 3rd and 4th respectively in both TTFB categories, which we’re pretty happy about.

Of course, we do offer a selection of geolocation options on our base plan. So if you value speed in, say, the US East Coast ,or the UK, or Germany the most – we should hopefully win that for you with our geolocated servers.

Taking price into consideration…

If cost wasn’t an issue and we had to pick an overall winner from the testing, it would have to be Kinsta, as they took home first place in two of the three hosting performance categories. Nice work Kinsta!

But, of course, if we’re comparing apples with apples we have to also look at pricing. Which, handily, we include below:

Another look at the test results, and host prices as well
*Sept 2020, managed WP plans, renewal prices, annual discounts applied, rounded up.

A few notes on the pricing:

  • It’s accurate as of September 2020.
  • All prices are in USD and retrieved via US VPN in incognito.
  • We’re only listing renewal prices (no initial discounts or multi-year lock-ins) but we are including annual discounts.
  • We’ve rounded up .99 (GoDaddy & BlueHost) and .95 (HostGator).
  • Cloudways is not a managed WP platform but is included due to our members usage, so site limits don’t apply, we’re choosing Digital Ocean with them.

So… how does WPMU DEV hosting rate now?

Considering the cost, we’d like to think that we offer the best value for money in terms of performance and load.

While Kinsta is obviously great choice for high performance on their base plan, you’d have to realistically test them against our silver or gold plans ($18.75 and $37.50 respectively) if you’re looking at a fair comparison.

GoDaddy is clearly fast (their CDN is great too btw) but we reckon we’ve given them a good run for their money.

But probably, after all this, we’d say that the host that’s most comparable with us is Cloudways because, well, we use the same partner (Digital Ocean) and as you can see we rank very similarly.

A big advantage for some users for Cloudways would be that you can install as many applications as you like on a Digital Ocean platform, whereas with us you just get the 1 WordPress site. However, that has enabled us to build a stack that vastly outperforms them when it comes to load testing.

Overall though, we’d say that either our hosting or Cloudways is probably your best bet based on these tests… although you could do a lot worse than using Kinsta or GoDaddy.

Our take on how WPMU DEV Hosting did.

Dev Man celebrating his (almost) win
Even though WPMU DEV didn’t come out on top in terms of performance, we’re still wrapped with the results.

Overall, we were really pleased with how WPMU DEV Hosting fared against the competition.

But that doesn’t mean that we can’t do better. In fact it’s energized us to try harder and get you better results.

Specifically we’d like to improve:

  • Our pricing… we’re working to offer you an even more affordable plan that delivers similar results (and better than our competitors).
  • Our TTFB… we’re adding new locations as I type this (Australia we’re coming for ya soon) that should improve our overall speed.
  • Our overall offering… in addition to all of the above, we’re hoping to provide you, by the end of the year, a managed WP platform for free on top of this.

As amazing as it would have been to take out first place and rule everything, in the grand scheme of things, we’re still new to hosting (just over a year old in fact!), and to already be up there with the best in the biz feels great, and we’re excited about doing better.

Some other key takeaways from this host performance testing experience:

  1. We feel like a lot of hosts rely too heavily on caching or CDN mechanisms to save them, but that they give you an unrealistic feel for the capacity of your hosting in a genuine and dynamic sense… anyone can serve a static html page to a bazillion visitors.
  2. TTFB is hard to measure fairly, it’d be great if more hosts let us know *where* they were hosting you for their base plan.
  3. We reckon the number of clients your server can handle is MORE important than the speed at which you’re serving them. Back to our bar analogy: Would you rather server 140 people in a timely manner? Or serve 40 at a slightly faster pace before 41 enters, and you’re forced to close and deny more potential customers?

Check out the full comparisons of each host vs. WPMU DEV Hosting.

A preview of our WPMU DEV compared page
Our comparison page gives you a full view of WPMU DEV vs. a range of other hosting options.

As touched on earlier, when comparing hosts it’s important to take EVERYTHING into account, not just performance.

So at the same time as running these performance tests, we also put together some insightful hosting comparison pages which square DEV hosting off against all the hosts mentioned above.

What’s great about these pages is that as well as the performance results, we’ve also included up to date feature and cost comparison tables you can use as reference.

That way you get a well-rounded idea of what host is going to suit you or your business best. So definitely check them out if you get a chance.

Let’s do this more often…

And that’s all there is to it.

We hope you’ve enjoyed this inside look at how we tested WPMU DEV Hosting.

Our team has taken a lot of valuable insights from this experience, and we hope you did too.

Anything you’d have us do differently? Were there some big hosting players we left off the list?

Let us know below.

The whole point of this process has been to be completely fair and transparent with all of our processes and findings. And if you think there’s a better (or fairer) way we could have tested, please let us know, we’re open to discussing anything and everything in the comments!

But in fact, you really don’t even have to take our word for it…

See how WPMU DEV Hosting performs for yourself.

If our findings have piqued your interest, feel free to run your own tests following our methodology (or any other you prefer).

Check out our hosting plans or take a WPMU DEV membership (Incl. 1 Bronze level site) for a free 7 day trial.

Want to test for longer than 7 days? Everything WPMU DEV comes with an automatic 30 day money-back guarantee.

Until the next round of hosting testing.✌