An experiment bunching some of Next's client-side code into a single repository.


7249.1.12 years ago5 years agoMinified + gzip package size for @financial-times/n-ui in KB



CircleCI Coverage Status

Yak button

⚠️ This component is deprecated. Please use Page Kit to build new applications. Only bug and security fixes should be committed to this repo. If you need to add new features an application powered by n-ui please speak to the Core UI team. ⚠️

Server, build and client side bootstrapping for ft.com’s user-facing applications.

PLEASE DON’T USE THIS OUTSIDE OF USER-FACING FT.COM APPLICATIONS. If you need a good Express server with Handlebars, metrics etc available consider using n-internal-tool, n-express or copying what you need from n-ui


n-ui has three parts – a server, a client side “app shell” (JS, CSS & Handlebars layout), and a build. Expect things to break if you don’t use all 3.


n-ui is a wrapper around n-express which adds templating and asset-loading features.

npm install @financial-times/n-ui

const app = require('@financial-times/n-ui')(opts);

app.locals.nUiConfig = {
    preset: 'complete', // 'discrete' will turn off ads & various popups
    features: {
        lazyLoadImages: true // turns individual features on/off. Check `/browser/bootstrap/js/component-initializer.js` for an up-to-date feature list

Where opts is an object supporting all n-express’s options, but with many set to true by default (see /server/index.js for details). Additional options include:

  • partialsDirectory String or array – path[s] to load partials from, this in addition to the standard views/partials that is set for every app
  • layoutsDir String – a path to load Handlebars views from defults to node_modules/@financial-times/n-ui/layout
  • withJsonLd Boolean – output jsonLD schema information in the page head


n-ui comes bundled with its own build tool – basically webpack, haikro build and a little bit of other stuff. To use it, add the following to your Makefile:

    nui build

    nui build --production

    nui watch

Custom build config

It is possible to use a add custom webpack-style configuration file to your project. Simply add a n-ui-build.config.js file in the root of your project. This supports the following properties:

To define entry points for your assets, use a n-ui-build.config.js file in the root of your project, which can export any object compatible with webpack.


App bootstrapping

n-ui takes care of loading polyfills etc, in order. n-ui exports 4 things you’ll want to use:

  • flags – the feature/development/maintenance/MVT flags object
  • appInfo – metadata about the app that's serving the page
  • allStylesLoaded – a promise that resolves once all the lazy-loaded styles are in place
  • onAppInitialized [required] – a function to call once the app js has successfully executed. This tells integration tests when the page is “complete” among other things

import one or more of the above from n-ui in your application code, which no longer needs to be wrapped in a function.


import { flags , allStylesLoaded, onAppInitialized } from 'n-ui';

if (flags.get('feature')) {

    .then(() => {
        onAppInitialized(); // it’s up to you to define when your app is “ready”


bower install n-ui

Nothing fancy going on here anymore 😄. No mixins (though the n-ui-foundations module has a few you will want to use), no tricky critical path CSS stuff.

@import "n-ui/main";

This will, when using the n-ui build tool, split n-ui’s styles into head-n-ui-core.css and n-ui-core.css files, and the server will inline/link to these appropriately.

Local development

Working in n-ui

You should be able to work in n-ui as if it’s an app – make watch and make run should work and serve a demo app on local.ft.com:5005.

Testing in an app

In local n-ui:

  • make install
  • make build-css-loader
  • npm link
  • bower link

In the app (e.g. next-article):

  • export NEXT_APP_SHELL=local
  • bower link n-ui
  • npm link @financial-times/n-ui
  • make build - !! need to do this for each change in either n-ui or front facing app.
  • make run

To test the cli locally, use ../n-ui/bin/nui.js build or ../n-ui/bin/nui.js watch from within a local copy of a user-facing app. To test the rebuild command first set the CIRCLECI_REBUILD_KEY environment variable using export CIRCLECI_REBUILD_KEY={key} (you can find the key in n-ui's continuous-integration folder in Vault). You can then use ./bin/nui.js rebuild next-search-page within your local copy of n-ui (perhaps comment out the call to triggerMasterBuild in the rebuild function when testing the ./bin/nui.js rebuild --all --serves user-page command so you avoid actually causing all apps to rebuild ;)).

Releasing n-ui

When you release an n-ui tag, 3 things happen:

  • assets are built and deployed to s3, from where they are linked to/downloaded by apps
  • the npm package is published
  • during work hours (9am to 4pm), all user-facing apps are rebuilt to pick up the changes

Server side APIs

Linked Resources (preload) res.linkResource(url, meta, options)

Adds link headers to optimise requests for assets, defaulting to preload behaviour:

  • url – absolute or relative path to the resource
  • meta – object defining additional properties to add to the header
    • rel [default: 'preload'] - value of the rel property
    • as – value of the as property e.g. 'stylesheet'
  • options – additional options when creating the header
    • priority – a value of highest will add the link header before all previously added resources that do not specify this (should not normally used by apps – used internally to ensure n-ui’s resources are always loaded as quickly as possible)
    • hashed – if true the path to the asset will be resolved to the equivalent hashed asset path


If you pass withNavigation:true in the init options, you will have navigation data available in res.locals.navigation. This data comes from polling the navigation API. This data is used to populate the various menus and navigation items on the apps. The following data is available:

    res.locals.navigation = {
        lists: {
            navbar_desktop: // data for the main nav in the header (only on large screens)
            navbar_mobile: //data for the white strip that appears on the homepage and fastFT pages only on small screens
            drawer: //data for the slide-out menu
            footer: // data for the footer


See the MyFT page for a rendered copy of this config.

res.locals.navigation = {
    // other settings here...

    showSubNav: true,
    // this populates the breadcrumb section at the left of the subnav
    breadcrumb: [
            id: 'my-ft',
            label: 'My FT',
            url: '/myft/following'
    // this populates the current level of subnav
    subsections: [
            id: 'feed',
            label: 'myFT Feed',
            url: '/myft/following'
            id: 'alerts',
            label: 'Emails & Alerts',
            url: '/myft/alerts'
    // optionally, add a 'Sign out' link to the right of the subnav (default is off)
    showSignOut: true

Navigation Hierarchy

If you also pass withNavigationHierarchy: true in the init options you get some additonal properties detailing the current page’s position in the hierarchy. This is only currently useful on stream pages. The following properties are added:

    res.locals.navigation.currentItem // the current item
    res.locals.navigation.children // an array of the direct descendant of the current page
    res.locals.navigation.ancestors // an array of the parent items of the current page (top-level first)


The navigation model also controls the edition switching logic. The following properties are added:

    res.locals.editions.current // the currently selected edition
    res.locals.editions.others //  and array of other possible editions

Header with clickable logo

In same cases you might need to show only the FT logo in the header, and hide all other navigation. This pattern is used in several conversion apps.

    nUi: {
        header: {
            variant: 'logo-only'
Header with not-clickable logo, and hide footer

If your page will be linked to from the iOS app, and no outbound navigation from it is allowed, then the flag 'hideOutboundLinks' will be set to true for you. This will render the header logo without it being a link, and hide the page footer.

Footer modifications

Similar nUi controls exist for footer. Current configuration allows footer theme changes and option disabling.

    nUi: {
        footer: {
            themeLight: true,
            legalOnly:  true

Other enhancements

  • Our Handlebars engine loads partials from bower_components and has a number of additional helpers. It also points to n-layout to provide a vanilla and “wrapper” layout
  • Exposes everything in the app’s ./public folder via ./{{name-of-app}} (only in non-production environments, please use next-assets or hashed-assets in production)

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.