
This is a report on how our bet on the skilled, rogue freelancer and the growing small agency has worked out as we approach our software-as-a-service's first year of operation.
We took a risk when we decided to build HiFi: create a professional grade software-as-a-service product in the website content management space dominated by consumer do-it-yourself, cookie-cutter, template driven products. While most content management system services are a play to displace professionals with software, HiFi's goal is to amplify professional web developers with better software and infrastructure. For too long the freelance and small agency status quo has been expensive self-installed, self-maintained software on overcrowded, underpowered servers. Our wild bet was that by killing it on the fundamentals of having a great JSON API, a powerful infrastructure, and best-in-class, knowledgeable support, we could alleviate the aspects of modern website site development that, frankly, suck (you know, things like maintaining server software and configuration, losing sleep over uptime and reliability, updating software to fix bugs and security issues, custom queries for data, effectively compiling and compressing CSS, LessCSS, JavaScript, and CoffeeScript, and so on), and help professional developers focus on what they love most: designing and implementing great web sites.
Only a year in it's still early to make a call, but so far the freelancers and agencies have invested in the switch to HiFi have not only loved the experience but have increased their productivity substantially. We're really encouraged not only in the feedback, but in the hard numbers. Publicly, for the first time, here they are...
Growth
Let’s start with number of websites powered by HiFi. For our purposes we define this metric as the number of live websites that received traffic. This means free trial sites and sites that are currently in production but not yet live are not counted. From May through November of 2010 HiFi was in beta. In less than a year since coming out of Beta the total number of web sites powered by HiFi has grown more than 350 percent.

Monthly page views is another interesting measurement we look. September was a record setting month for HiFi with over 2.5 million page views served. By the end of October HiFi should surpass 20 million page views served since inception.

Site Speed
We spent some time earlier this month improving our caching system so not only are we serving more page views than ever before, they’re getting served faster than ever before. Cached pages are now served ~100ms faster than before which represents a 5-10x improvement in page load speed depending on client internet connection. You can really feel the difference in clicking around sites on HiFi!

HiFi web sites are much more than just HTML pages, across the board each page contains beautiful graphics, meticulous CSS and LESS files, and powerful JavaScript and CoffeeScript programs. We serve all of these assets up, too. In fact, HiFi has served over 150,000,000 requests total across website front- and back-ends. During peak hours we average up to 33 requests per second with bursts of over 100 requests per second.
Reliability
We haven't found competing services that are publishing their exact monthly uptime records but we think it's important. This is something we lose sleep over, we're proud of, and in recent months HiFi is more reliable than ever. Since June 1st, 2011 we have a 99.998% uptime record. In the trailing year we're just a hundredth of a percent shy of that at 99.989%. Here's a look at HiFi's uptime, as measured on minute intervals by Pingdom, in the trailing year:

Bigger, Faster, Stronger
Our team is really pleased with how quickly the number of live public sites on HiFi is growing. Not only are we seeing sites grow their traffic by leveraging the baked-in features of HiFi, but we are seeing bigger sites moving to HiFi as well. This is evident in that from September of 2010 to September of 2011 our number of live sites has grown by 5x and overall page views by 8x. With recent updates we are serving these websites faster and more reliably than ever before and with no additional effort needed on the web site developer’s part.
If you plan to offer material on your website exclusively to designated users, then creating a member login section is a necessity. Luckily, HiFi makes it simple enough to set up a login area in a matter of minutes.
To start creating your login page, create a blank page in your site tree and title it something like “Member Login.”
The default HiFi template comes with a pre-built login form that appears anytime a user who is not logged in or registered attempts to access content only visible to site users. You can find the login.html file in the hifi > user folder of your template, or simply create a new one.
In the login.html file you’ll see the following form:
<form id="login" action="/hifi/login" method="post">
<fieldset>
<label for="email">Email</label>
<input type="text" class="text" id="email" name="email" value="{{email}}"/>
<label for="password">Password</label>
<input type="password" class="text" id="password" name="password" value=""/>
<input type="hidden" name="redirect" value="{{redirect}}" />
<fieldset class="checkbox">
<label><input type="checkbox" name="staySignedIn" value="false"/> Remember me</label>
</fieldset>
</fieldset>
<fieldset class="submit">
<input type="submit" value="Login"/>
</fieldset>
</form>
Feel free to change the fieldsets to those that apply to your users. For instance, maybe you’d like to replace the email input with a username field. Once your form is set, copy this code.
Next, you’ll need to create a custom template file for your “Member Login” page. Paste the form you just copied into the correct content area and change the value of the redirect input field.
For example, if you wanted to redirect users to the homepage after they have logged in, your code would look like so:
<input type="hidden" name="redirect" value="/"/>
That’s it! If you’d like to test the functionality of your login page make sure you are completely logged out of the site or open it in a new browser window.
Overview:
This snippet generates a timeline of the most recent tweets from a particular Twitter user utilizing HiFi's retrieve tag. It generates a url calls a JSON Object of a user timeline from the Twitter REST API. Then it parses the data in a format that mimics the format on the Twitter website including links and twitter handles. Because Twitter limits the number of requests per hour, the snippet is setup to cache the timeline every 15 minutes.
Components:
Template:
{% import "/snippets/twitter/twitter.html" as twitter %}
{{ twitter.draw({ handle:'gethifi' }) }}
Here is sample output including a standard tweet and a retweet:
<section class="tweets">
<article class="profile">
<img src="[ Image URL ]" alt="[ Display Name ]" width="73" height="73" />
<h2>[ Display Name ] w</h2>
<h3>@[ Screen Name / Handle ]</h3>
<p>[ Profile Description ]<br/><a href="[ Profile URL ]" target="_blank">[ Profile URL ]</a></p>
</article>
<article class="tweet">
<p>[ Tweet Text ]</p>
<div class="row">
<time datetime="[ Tweeted Created_At Time ]" pubdate>
<a href="[ Tweet URL ]" target="_blank">[ Parsed Timestamp ]</a>
</time>
<div class="actions">
<a class="retweet" href="[ Retweet Intent URL ]" target="_blank"><span><i></i> retweet</span></a>
<a class="reply" href="[ Reply Intent URL ]" target="_blank"><span><i></i> reply</span></a>
</div>
</div>
</article>
<article class="tweet retweet">
<div class="retweeted">
<div></div>retweeted
<time datetime="[ Tweet Timestamp ]" pubdate>
<a href="[ Retweet URL ]" target="_blank">[ Parsed Timestamp ]</a>
</time>
</div>
<img src="[ Original Tweeter Profile Image ]" width="48" height="48" alt="[ Original Tweeter User ]" />
<p>[ Retweet Text ]</p>
<div class="row">
<time>
<a href="[ Retweet URL ]" target="_blank">[ Original tweet timestamp ]</a>
</time>
<div class="actions">
<a class="retweet" href="[ Retweet Widget URL ]" target="_blank"><span><i></i> retweet</span></a>
<a class="reply" href="[ Reply Widget URL ]" target="_blank"><span><i></i> reply</span></a>
</div>
</div>
</article>
<div class="follow">
<a href="https://twitter.com/[ Twitter Handle]" class="twitter-follow-button" data-show-count="false">Follow @[ Twitter Handle ]</a>
</div>
<script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
</section>
Options:
handle:
Twitter handle without the '@' symbol. This is the only required option that needs to be passed when calling this macro. ex: {handle:'nmcteam'}, {handle:'gethifi'}.
count:
The number of items from the timeline to be displayed. You can have a maximum of 200.
Default:3
profile:
If true this will display the twitter profile, similar to the one you see on a individual profile page.
Default: true
includeRetweets:
If true the user's retweets will be included in the retrieved timeline.
Default:true
includeReplies:
If true, the user's @replies will be included in the the retrieved timeline.
Default:true
includeTwitterPlatform:
If true the template will include Twitter's JS platform that allows the user pop-ups and the follow button to function.
Default:true
containerElement:
HTML element that contains the widget.
Default: section
tweetElement:
HTML element that contains individuals tweets.
Default: article
profileTemplate:
Path to template used to display the profile.
Default:'/snippets/twitter/profile.html'
tweetTemplate:
Path to template used to display individual tweets from the timeline.
Default:'/snippets/twitter/tweet.html'
timeTemplate:
Path to template used to generate timestamps.
Default:'/snippets/twitter/timestamp.html'
For a long time, designers have been very limited when it comes to fonts on the web. There is just a small list of web safe fonts that reliably render correctly across multiple browsers and operating systems. If a designer wanted to use a font that wasn't on this short list, there were very few options, all having serious drawbacks.
Historic solutions to solve the font problem include replacing text with images, cufon, a technique for using javascript to render text and sIFR, which uses flash to substitute text with flash rendering of fonts. These are workable solutions, but not without their drawbacks. They display fonts in a non-native manner, causing text selection issues, increased hardware demands and dependencies on browsers/devices supporting a third party proprietary plugin.
In comes @font-face. @font-face is a web standard with increasing, and now-nearly universal desktop browser support. It allows designers to include and define fonts in CSS. The basic syntax is as following:
@font-face {
font-family: 'Foo Font';
src: url('foofont.otf');
}
h1 { font-family: 'Foo Font', arial, sans-serif; }
Once you declare a new font family, you are able to use it natively, just as you would a standard web-safe font. The above code assumes you have an opentype font file 'foofont.otf' in the same directory as the css file.
If you don't want to host the font files yourself, there are third party services - both free and paid - that will do it for you. The two services we most recommend are Google Fonts (free), which has many open source fonts and TypeKit (paid) which offers access to fonts from a number of popular foundries. These are simple to use and get you up and running right away.
Cross-Browser Compatibility
While it would be nice to simply use the code above and have @font-face just work, cross-browser support for font formats makes things a little trickier. The general syntax works in nearly all modern browsers, but the actual format the font files need to be in varies.
In order for fonts to render correctly across browsers, we need to include all of the supported formats - which looks something like this:
@font-face {
font-family: 'Foo Font';
src: url('foofont.eot');
src: url('foofont.eot?#iefix') format('embedded-opentype'),
url('foofont.woff') format('woff'),
url('foofont.ttf') format('truetype'),
url('foofont.svg#ChantelliAntiquaRegular') format('svg');
font-weight: normal;
font-style: normal;
}
Ugh. It's quite the pain to come up with all of these different font formats. Fortunately, there's an easy way to do this: Fontsquirrel.com's @font-face generator lets you generate the css and font files you need in just a few clicks. We'll cover @font-face generator usage in the next section.
Using @font-face on HiFi
HiFi serves all of its theme assets (css, scripts, images, fonts) from a separate subdomain: ui.domain.com. This is done as recommended by YSlow and Google PageSpeed. By serving files from multiple (sub)domains, you increase the browsers ability to make requests in parallel, downloading your site faster.
Unfortunately, Firefox doesn't like showing fonts that are served on a different subdomain from the page for security reasons, so it won't render a font from ui.domain.com on domain.com.
There is a workaround however! In fact, it also happens to speed up page load times: you can embed the font code directly into the CSS rather than keeping it in a separate file. This saves browsers from having to download additional file(s) AND it's compatible with Firefox. It works using Base64, an encoding scheme that lets you translate binary data into an ASCII string format. Here are the steps required to do it:
- Find and download the regular font files you'll need. These will be .otf or .ttf.
- Ensure you own the proper liscenses for the fonts you'll use.
- Go to the FontSquirrel @font-face generator: http://www.fontsquirrel.com/fontface/generator
- Click +Add Fonts and add in an otf or ttf file for each font you need.
- Click the 'Expert' option
- Under CSS Options, choose Base64 encode
- Check the license agreement
- Generate/Download the kit
- You now have a zip containing a CSS file and a bunch of different font formats. The font files all get used by obscure browsers and IE. Most browsers will use the base64 encoded font in the CSS.
- Rename the CSS file to something useful like 'fontname.css'.
- Open the CSS file
- You'll notice the giant blocks of code immediately. These are the base64 embedded fonts. You'll also notice a few references to the external fonts that get used by some browsers.
- These external font references account for the font files being in the same dir as the CSS file. This isn't the case with HiFi. You'll need to change url(fontname.woff) (or whatever) to url(../fonts/fontname.woff). Do this with a search/replace so you don't miss any.
- Upload the css file to HiFi under '/styles'
- Upload all the font files to HiFi under '/fonts'
- You now need to include the CSS file in your index.html above your other stylesheets. This is a rare case where you don't want to necessarily compile/minify the CSS together with everything else. Create a separate {% css %} declaration above your main one for your fontname.css file.
- Voila, you can now use the fonts like any other: font-family: 'fancy name', arial, sans-serif;
It may seem like a bit of work to do this, but once you get the hang of it you'll be able to start using new fonts very quickly. It's also good to know that by using this technique, your fonts will work reliably across desktop browsers and perform as well as they can.
Overview
Easily add static or animated captions to your images.
Requirements
Javascript and a jQuery library are required to use one of an image’s attributes to build the markup for a caption.
Components
CSS:
.caption{
font-size:12px;
font-weight:bold;
padding:0 0 20px 0;
}
Javascript:
$('img').jcaption({
copyStyle: true
});
Installation
Insert an image (maybe in a page or post) and fill in the image description, or alt attribute, with the text you would like to appear as the caption. Then upload the jcaption.js file to your theme and include it in your desired template. Add the styles included above to the stylesheet and the javascript directly to the template so you can add or edit the options that are provided below.
If you are floating your images, giving them padding or adding any other style, be sure to keep copyStyle set to “true” so the caption class does not override your image styles.
Options
$('img').jcaption({
// Element to wrap the image and caption in
wrapperElement: 'div',
// Class for wrapper element
wrapperClass: 'caption',
// Caption Element
captionElement: 'p',
// Attribute of image to use as caption source
imageAttr: 'alt',
// If true, it checks to make sure there is caption copy before running on each image
requireText: true,
// Should inline style be copied from img element to wrapper
copyStyle: false,
// Strip inline style from image
removeStyle: true,
// Strip align attribute from image
removeAlign: true,
// Assign the value of the image's align attribute as a class to the wrapper
copyAlignmentToClass: false,
// Assign the value of the image's float value as a class to the wrapper
copyFloatToClass: true,
// Assign a width to the wrapper that matches the image
autoWidth: true,
// Animate on hover over the image
animate: false,
// Show Animation
show: {opacity: 'show'},
showDuration: 200,
// Hide Animation
hide: {opacity: 'hide'},
hideDuration: 200
});
Overview:
Show a location on a Google Map by providing just an address.
Requirements:
Javascript is needed to use Google's API for Geocoding the location.
Components:
Template
<div id="map-container">
<div id="google-map"></div>
</div>
<script>
<!-- Insert Javascript Component Here -->
</script>
CSS:
#map-holder{
background:#fff;
border:1px solid #c5c5c5;
display:block;
height:400px;
margin:20px 20px 0;
padding:5px;
width:960px;
}
#google-map{
display:block;
height:100%;
}
Javascript:
var map;
var geocoder;
var address = '{{ this.custom.googlemaps.address }}'; // Replace the variable with your HiFi Custom Field
function initialize() {
var latlng = new google.maps.LatLng(40.111676, -98.349584);
var myOptions = {
zoom: 4,
center: latlng,
panControl:false,
zoomControl:false,
scaleControl:false,
mapTypeControl:false,
streetViewControl:false,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("google-map"), myOptions);
geocoder = new google.maps.Geocoder();
geocode();
}
function geocode(){
geocoder.geocode({'address': address,'partialmatch': true}, geocodeResult);
}
function geocodeResult(results, status) {
if (status == 'OK' && results.length > 0) {
map.fitBounds(results[0].geometry.viewport);
var marker = new google.maps.Marker({
position: map.getCenter(),
map: map
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
}
function loadScript() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://maps.google.com/maps/api/js?sensor=false&callback=initialize";
document.body.appendChild(script);
}
window.onload = loadScript;
Installation:
Add the <div id="google-maps"> onto your template and the styles to your stylesheet. Then you'll want to create a custom text field for the address on any pages you want to display the map. Then add the javascript directly to the template so you can have access to custom field variables, and change the address variable to your custom field tag.
Overview:
This snippet allows you to easily create a grid of images from the media tab. You upload however many image you want, and then using custom fields, specify how wide you want the container to be and the number of images you want on each row.
All of the images are then automatically resized and placed in the grid.
Requirements:
No external requirements.
Components:
Template:
{% include 'common/image-grid.html' with ['images':this.media, 'width':this.custom.tych.width, 'layout':this.custom.tych.layout] %}
Installation:
To install this snippet, you just need to upload the file in the zip to your theme and include it in your desired template. All of the required CSS and image resizing is done inside the template itself.
You need to make sure that you're passing in variables for images, width and layout. This means that you need to set up custom fields for width and layout.
- width: This is the width of the container that holds the images. Use a text field for this.
- layout: This is a textarea that explains how many images go on each row.
All the magic happens in the layout field. The following would display 5 images total, 2 on the first line, 3 on the second:
2
3
It is also possible to indicate how tall you want each row to be in pixes. You can optionally do that by using a bar ("|") and then putting in the number of pixels you want. Here is an example that displays 6 images, 1 on the first row, 2 on the second, 3 on the third. The second row is set to 100px high:
1
2|100
3
Here is a screenshot showing the custom fields. Note that I am also using an "Enabled" dropdown so that it runs optionally.

Overview:
This snippet is a core part of every HiFi theme. It allows you to easily build navigation, menus, sitemaps or any feature that needs a nested list of your site content.
The idea behind the snippet is that you provide a piece of content as a starting point and a depth, and this will build a tree for you. Typically, the starting point will be your homepage, and the depth will be 1 or 2 depending on whether you want drop downs in your navigation.
By default, this snippet generates nice and clean markup, but you can pass in other templates if you need to change that markup dramatically. Most common things, like adding ids and classes can be done with the default templates.
Requirements:
No external requirements -- although this snippet won't do you any good if you're not on HiFi.
Instructions:
Template:
The default options will generate nested unordered lists starting from your homepage, two levels deep. Here is how you make the default call:
{% import '/common/navigation/navigation.html' as navigation %}
{{ navigation.draw() }}
This will generate something like the following:
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a>
<ul>
<li><a href="/about/philosophy">Philosophy</a></li>
<li><a href="/about/history">History</a></li>
<li><a href="/about/team">Team</a></li>
</ul>
</li>
<li><a target="_blank" href="http://www.yahoo.com">Yahoo</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
If you need to do something beyond the defaults, you can add in options by passing in an options object. Here is an example that changes the maxDepth to 3 and has two other fake options. It doesn't matter what order they are in.
{% import '/common/navigation/navigation.html' as navigation %}
{{ navigation.draw({
maxDepth: 3,
option: 'value',
otherOption: 'value'
}) }}
Options Reference
root:
A query to find the starting point for the nav. So {type: 'page', url: '/about'} would start from the about page. It uses the first result from the query.
Default: {type: 'home'}
maxDepth:
The number of levels this should traverse.
Default: 2
childQuery:
This is the query used to restrict the items that are found. It is used at each depth level.
Default: {type: 'content', inMenu: true, orderBy: 'ordinal', count: 100}
activeClass:
The class to put on the active nav item based on url.
Default: 'active'
ancestorActiveClass:
The class to put on ancestors to the active nav item based on url.
Default: 'active'
includeParent:
If true, the root is included in the first level nav.
Default: true
includeLevelClass:
Display a level class on each li (ie. class="level_3")
Default: false
levelClassPrefix:
Specify the string you want to prefix the level number
Default: 'level_'
cssId:
The id attribute assigned to the nav's first ul
Default: ''
cssClass
The class attribute assigned to the nav's first ul
Default: ''
newWindowAttribute:
For link types that have newWindow set, what attribute do you want to add to the link.
Default: 'target="_blank"'
linkFolders:
Specify whether folder links should have the href attribute
Default: false
wrapperTemplate:
Path to the template to use for wrappers
Default: '/common/navigation/wrapper.html'
itemTemplate:
Path to the template to use for items
Default: '/common/navigation/item.html

This morning we're making it official the HiFi Snippet Library has launched.
A great thing about working with HiFi is that you can use the web technologies you know (HTML/CSS/JS) with no restrictions. Many systems say this, we actually mean it. This means that it is extremely easy to build reusable snippets that can save you all kinds of time when building your client sites.
If none of this makes sense to you, hopefully we can answer your questions here. If you have any others, let us know in the comments.
What is a snippet?
A snippet is a combination of HTML, CSS and JS that you can drop into your sites' templates to quickly implement a feature. Since it is based in just HTML/CSS/JS, it is easy to customize or change them.
Why is this cool?
Snippets let you quickly add functionality to your sites without forcing you to use particular HTML. Design and code the sites the way you want, and then hook up the functionality with snippets.
How much do they cost?
The snippets are completely free. Most can even be used on non-HiFi sites!
Where are they coming from?
The HiFi snippets are being written both by the HiFi team as they build sites, and by customers using HiFi. If you have a snippet you would like to see, or a snippet you want to contribute to the library, get in contact.
How often will they be released?
We have committed to release at least one snippet a week, but through community contributions, the number may be even higher! Whenever we release a snippet, we will be posting it to the HiFi blog. So be sure to subscribe to the blog to get updated on the newest snippets.
Your snippets are stupid. Why don't you have an XYZ snippet?
We like your attitude. Shoot us a message and we'll make sure it gets added!