If you create a Facebook application, one of the most confusing parts of setting it up is choosing whether to make your app use IFrames or FBML as the default for your application’s canvas pages.
The text explaining the choice is:
Your application will be viewable in the Facebook navigation at this URL – either as rendered FBML or loaded in an iframe. If you aren’t sure what you should use here, choose FBML. You can use iframes within FBML on canvas pages with the <fb:iframe> tag, and most things you will want to do will be easier and faster with FBML.
But over the last few months, there have been a few developments which have made that explanation of the choice out of date. I think it is now easier to create a better app by using IFrame canvas pages than it is with FBML. I’ll try to explain the difference between the two paradigms, some of the things that made FBML a better choice originally, outline what’s changed since then, and make some conclusions about what techniques you can use to make the best performing applications. Ray wrote a post on the official Facebook Developers’ Blog about enhancing IFrame-base applications a few weeks ago, which overlaps a lot with this post, but I think its worthwhile to go into a little more detail around the choice between FBML and IFrames.
How a traditional IFrame canvas page worked
IFrame canvas pages are pretty straightforward. When the user loads a page on Facebook like http://apps.facebook.com/yourapp/somepage, Facebook serves up a webpage that has the Facebook chrome around a big IFrame. The IFrame that gets opened is something like http://www.yourdomain.com/callbackurl/somepage?fb_sig_in_iframe=1&fb_sig_locale=en_US&fb_sig_time=1221720862.9318&fb_sig_api_key=48102584111d14a9c2e41dd28ea637d7&fb_sig=e656792696ae913c1fc34eeff2d79f75
The fb_sig parameters give more information about which user is logged into your application, let you verify that the request is indeed coming through Facebook, etc. You can put whatever you want into the Iframe and it will be rendered there.
Usually in a Facebook app, you’ll want to include some social content like names and profile pictures, and sometimes you’ll want to get some data from the Facebook API to determine which content to show. The way you can show this content without using FBML is to get the information you need via the API and render it yourself.
Here’s a rough diagram of how all this works:
Traditional Iframe Canvas Page
How an FBML canvas page works
FBML canvas pages are a little bit different but are still pretty straightforward way. When the user requests a page like http://apps.facebook.com/yourapp/canvaspage, Facebook doesn’t send back a response right away; instead, the Facebook server will send a request to your app’s server. This will be an HTTP POST to some URL like http://www.yourserver.com/callbackurl/canvaspage. The fb_sig parameters will be sent as part of the POST rather than as part of the URL. Facebook will expect some FBML back and then turn that FBML into HTML and send it back to the user’s browser. FBML is mostly the same as HTML, but using FBML lets you include social content inline in your markup.
So, if you want to show things like names and pictures in FBML, you don’t need to make calls to the Facebook API; you can just use tags like <fb:name> and <fb:profile-pic> to reference the data directly. If you want to use data like birthdays, etc., then you’ll still need to use the API the same way you would on an Iframe page.
Here’s a diagram of how an FBML canvas page works. A lot of the time, you won’t need to make any API calls, so those arrows are dotted in the diagram.
FBML Canvas Page
Why We’ve Been Recommending FBML Up Until Now and What’s Changed
There are a bunch of nice things that FBML provides for you which is why we’ve been recommending that developers use it. But over the past few months, we’ve been doing a lot of work–mostly as part of Facebook Connect–building stuff that you can use to get most of those advantages and more for your IFrame canvas pages. The ones that I think are most important are:
FBML pages have tended to be faster for a bunch of reasons. For one thing, many FBML canvas pages don’t end up needing to make any API calls, and when that happens there is one fewer roundtrip that needs to be made if you are using FBML. Another thing that has tended to make FBML canvas pages perform better is that Facebook’s servers are directly peered with most of the large hosting companies that serve app pages, so the latency on the roundtrip from Facebook’s servers to your application can be much smaller than the latency from the user’s browser to your application’s server. There is also some non-trivial overhead from setting up an Iframe on most browsers but I believe this is fairly small compared to the other two things listed here.
What’s Changed: Facebook Chat and XFBML
We developed XFBML as part of Facebook Connect as a way to quickly get social content onto any webpage, and since IFrames are just webpages, XFBML can speed those up as well.
Here’s a diagram of how an IFrame canvas page works using XFBML on the first page a user loads from your app:
IFrame Canvas Page Using XFBML - First Page Load by a User
At a glance, this looks worse than the scenario for Iframe canvas pages that don’t use XFBML, but if you use XFBML, then you usually won’t need to make an API call to Facebook from your server (which means (4) and (5) don’t happen), and the user’s browser will be able to start rendering most of the page–everything except the XFBML content its waiting to get from Facebook–right after (6).
On subsequent page loads, the benefits are even greater. Here is a diagram of what that looks like:
Iframe Canvas Page Using XFBML - Subsequent Page Loads by a User
Note that in this case, the original request to Facebook and response to the browser don’t need to happen anymore, and the JS API calls will sometimes be unnecessary here as well if the necessary data has been cached on the client. In this scenario, your app should be basically just as fast as a regular website except that the social content will fill in just after the page has rendered which can result a little flicker on some browsers.
We’ve heard from developers that FBML has been simpler than using the API in two ways.
FBML is really nice when its important to batch your data access
It is actually a pretty gnarly problem to figure out all the data you are going to want on a page up front, and as your pages get more complicated, this problem gets more complicated too. One solution is to produce code that has two sections: the first which figures out all the data you need and fetches it from the API and then stores the data in some variables in your program, and the second part which renders all the HTML that you want using the data stored in those variables. This technique can work, but it is a little bit tedious and hard to maintain if you want to change anything since you have to make changes in two places. You’re also likely to make mistakes in this scenario where you remove something from the rendering section but forget to remove the corresponding data access entry and then are requesting data you don’t need which is hard to notice and inefficient. Things get even worse if you try to build modules that can be reused across different pages. An example of this would be a module that displays which of your friends are online and prompts you to interact with them. In that scenario, you either end up writing two sections of code for each of your modules, which is the same pain as described above but multiplied by the number of modules you have.
The other solution you could use besides a two-pass approach would be to create a system of placeholders that describe the data they need. If you do this, you can have one piece of code that doesn’t change that scans all the placeholders and makes a big request for all the necessary data and then fills it in right before you send your rendered response back. This is essentially what FBML is, and so FBML is convenient in that we do this for you and optimize it as much as we can so you don’t have to build this system or work on it.
FBML is nice for Facebook-provided widgets and dialogs
There are ways to render things like the multi-friend selector widget and feed forms when using Iframes, but these are often a bit clunky compared to doing these in FBML. There are also some useful widgets that are provided as part of FBML like fb:share-button that are a pain to recreate on your own (though certainly possible). I’ve heard from developers that fb:dialog is a really nice way to make a popup dialog with a few options. If you want to make an application that is consistent with Facebook’s look and feel, these tags can make that task much easier (though some of them can cause headaches because they aren’t tweakable enough).
What’s Changed: XFBML gives you access to all this within Iframes
In addition to being able to embed things like fb:name and fb:profile directly into your HTML which makes doing efficient data access easier, you can use any FBML tag in XFBML by using server-side FBML. If you wrap your FBML inside the tags <fb:serverfbml><script type="text/fbml"> ... </script></fb:serverfbml> then an Iframe will be opened to Facebook on the page being served from your domain, and we’ll render the FBML in there.
There’s a fair amount of overhead involved in opening the IFrame and your CSS won’t propagate into the FBML IFrame, so you won’t want to put too many blocks of server-side FBML on a page, but if you just need one or two snippets of FBML to make your life easier, or you want a large chunk of your page to use FBML, this makes that possbile and pretty easy.
One thing that often makes IFrames look ugly is that they have to be declared to have some fixed size and then if any content exceeds that size, nasty scroll bars appear (or the overflowing content is inaccessible to the user). Early IFrame apps on Facebook often looked very ugly for this reason despite a cursory “smartsizing” solution that we implemented. Most developers ended up creating giant IFrames with heights larger than they ever expected their content to get which was a little better but obscured the page footer and made the scrollbar unintuitive.
What’s Changed: Now, there’s an easy way to get your IFrame to resize automatically
It’spretty common to know what FQL queries you need to make to generate a page even before the request comes in from a user. For example, if you want to make a page that shows a calendar of friends’ birthdays, then you know you’ll need to make an FQL query that returns all the birthdays of all the friends of the user. We added a feature about a year ago called Preload FQL that has let you do this on FBML canvas pages. You just have to specify a few regular expressions that map to lists of FQL queries, and if the page being requested matches any of the regular expressions, the corresponding FQL queries will be executed by Facebook when the user’s browser sends the request to Facebook and the results will be sent along to your app’s servers when Facebook requests the FBML from you. Most of the time, this makes calls to Facebook’s API unnecessary, even when you need fairly complicated data from Facebook to generate the page.
This is what the flow would look like for an FBML canvas page using preload FQL:
FBML Canvas Page Using Preload FQL
In the diagram above, the FQL query is executed after (1) and the results are sent along to your application server with (2).
What’s Changed: Now, there is a way to do something similar in IFrames
Here’s a diagram of how this works:
IFrame Canvas Page using Preload FQL
Note that the preload FQL will only work on the first page that loads in your IFrame. (If you point your links to apps.facebook.com URLs using target="_top" instead of just linking to different pages within the frame, you could use preload FQL on every page, but this would make all your pages load slowly. We may make some mechanism for directing the containing frame to make API calls which could then be fetched across pageloads within the IFrame if there is a lot of demand for that. In the meantime, if this is important to you, it should be possible to roll your own mechanism for this by wrapping your IFrame canvas pages within your own extra IFrame.)
We also send the list of the user’s friends to the containing frame right away so calling the friends_get method from the JS API client also won’t require a roundtrip to the server now.
One thing that’s annoying about IFrame canvas pages is that when a user navigates through your app, the location in the URL bar never changes; it always stays on whatever the user went to (unless you specify target="_top" on your links, but that means that you’re incurring all the costs of a first page load again which is much slower.)
On FBML canvas pages, the request for FBML that is sent to your application server contains POST data that lets you learn and verify that the request is coming from Facebook on behalf of a particular user. If that user has a session with your application, then the session data you need to make API calls on behalf of the user will be passed along as fb_sig_session_key in the POST. Most client libraries handle this pretty seamlessly, and so you don’t have to think about it.
On IFrame canvas pages, things don’t work as well. Facebook passes fb_sig_session_key to your IFrame as a GET parameter in the querystring of the IFrame’s URL. As with FBML canvas pages, most client libraries make this pretty painless and invisible to the app developer; but there are two pretty big flaws with this approach.
Browsers can leak the session key via the HTTP_REFERER field. This is a pretty serious security flaw in our design, and it is pretty easily exploitable if you let users post public links or images. One way you can fix the problem with links is by sending all links through a redirect, but that doesn’t solve the problem for images. It is currently not safe in an IFrame canvas page to reference images that users have given you the URLs for without proxying or caching those images yourself.
The other problem with passing the session information around as part of the URL of your page is that it makes it inconvenient to link to pages within your app without losing the session information. One way to handle this is to make your links point to canvas pages (like http://apps.facebook.com/youriframeapp/nextpage.py) and set target="_top" on the link. This technique means that you don’t lose session information but means that your application incurs all of the overhead of setting up an initial IFrame canvas page which leads to a pretty clunky feeling application experience. Another technique you can use is to link to other pages that are off your canvas URL (ex. http://yourdomain.com/appcallback/nextpage.py) and manually append all of the fb_sig parameters to the querystring. The major problems with this approach are that if a user opens that page in a new tab or copies and pastes any of those links, then the Facebook chrome gets lost, and all sorts of problems can happen with the session information (the session may have expired, the session might be for a different user than the person viewing the URL, etc.), so this isn’t a desirable choice either.
How to Deal with This Problem
Update: Ted Suzman reminds me that setting cookies doesn’t work in some browsers (Safari) for IFrames of domains other than the one in the location bar, so this solution won’t work for all browsers. I’m sure there’s some other good solution to this though.
I think we’re fairly likely to get around to implementing something to make this easy in the next month or two, but until then, it might be worth rolling your own workaround for these issues.
What All This Means
Now that we’ve made all these improvements that can make IFrame canvas pages better, here is how I would list out the advantages of the two methods:
Is likely to be faster on first page loads
Fewer moving parts are involved and the paradigm is closer to that of the traditional web
Gives you easy access to lots of Facebook elements
Lets pages in your app have nice URLs
Has a sensible authorization mechanism
Is likely to lead to a faster experience for users over the long run
That said, there are a number of things we’re likely to release in the future that will bring FBML and IFrames even closer to converging on a point of parity. I’m not sure of a timeline for any of these things, and this list certainly isn’t exhaustive, but some obvious improvements that we should execute on are:
Making a mechanism for an application IFrame to communicate page transitions to the outer frame so that the URL changes in a linkable way when a user navigates around an IFrame application.
For IFrame apps, building a way for API calls to be executed in the outer frame so that results can be requested prior to page transitions of the IFrame. It might also be useful to provide a general cross-page client-side caching mechanism by attaching cached data to the containing frame.
Once those things are done, FBML and IFrame canvas pages should be similar in performance and ease of use. And now that fb:serverfbml has been added to complement fb:iframe, what you choose to use for your app isn’t that important since you can switch between the modes fairly easily. For most applications, I don’t think IFrames are so much better right now that it would make sense to convert an existing FBML-based application to one using IFrames instead, but if you’re unhappy with the speed of your app, it may be worth a try.
In brief, if you use the new features we recently released, using IFrames for canvas pages is probably a little bit better choice than using FBML for canvas pages.
I’ve heard from a few developers who’ve switched over to IFrames and used the new stuff that they’ve seen good results doing this, but I haven’t had time to do any benchmarking myself. If you try switching from FBML to Iframes and measure your performance before and after, please leave a comment about how much faster (or slower) the load time became. If IFrame performance is significantly better for most apps then it might be worth doing the conversion from FBML to IFrames for some developers.
Two Last Tips
If you’re using FBML for your canvas pages and you decide you want one of your pages to just be an IFrame, don’t use fb:iframe for that. Instead, you should use the fb_force_mode querystring parameter to switch modes which will accomplish the same thing without incurring a roundtrip to your server just to fetch the fb:iframe tag. Similarly, if you are using IFrames for your app but want some particular page to be an FBML page, you can use fb_force_mode to force a switch to FBML. If you find documentation that advises using fb:iframe for switching from FBML to IFrame mode, it was probably written before fb_force_mode was released; we should have all those instances cleaned up soon.
If you’re using IFrames for your canvas pages, don’t set all the links to have target="_top". That will cause a full reload of the Facebook chrome plus an extra to request to do that and generate a new IFrame which will be pretty slow. If you are using an IFrame within an IFrame (or even deeper nesting) and so you need to set a link target explicitly, the IFrame that is your canvas is named iframe_canvas so you should just be able to use that as a target.