Last Updated: February 25, 2016
· optilude

Meteor code organisation

Some "current thinking" on how best to use Meteor (


Use Meteor 0.9, which includes the package management features previously found in Meteorite.

Some useful packages (install with meteor add)

Code organisation

Here is a simple code layout that fits my way of thinking:

├── client/              Client-side-only assets
│   ├── layout.html      Defines an overall page layout for routed pages
│   ├── notfound.html    A default "not found" page for routed requests
│   ├── *.html           Top level pages rendered by the router
│   ├── components/      Shared templates used by top level pages
│   │   ├── *.html
│   ├── css/             Custom stylesheets
│   │   └── *.css
│   ├── js/              Client-side JavaScript logic
│   │   ├── *.js         Template helpers and events for components
│   │   └── startup.js   `Meteor.startup()` and `iron-router` configuration
├── lib/                 Shared library code, loads early
│   └── _namespaces.js   Defines global namespaces
├── models.js            Shared model/collection definitions
├── server/              Server-side-only methods
│   └── *.js

lib/_namespaces.js defines top level namespaces. See For example:

/* global Models: true, Collections: true, Schemata: true */

// This file exists so that we can do "use strict" in all other files and
// still have some global namespace variables.

// It is called what it's called and placed where it's placed so that it loads
// as early as possible.

Schemata = {};
Collections = {};

models.js (or possibly a directory of files if there are many models) defines collections and schemata. For example:

/* global Collections, Schemata, SimpleSchema */
"use strict";

Schemata.MyModel = new SimpleSchema({



Collections.MyModels = new Meteor.Collection("MyModels", {
    schema: Schemata.MyModel

if(Meteor.isServer) {
    Meteor.publish("models", function() {
        return Collections.MyModels.find({});
} else if(Meteor.isClient) {

Here is an example of a client/layout.html that uses a simple Bootstrap 3 template:

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">


    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src=""></script>
      <script src=""></script>

<template name="layout">

    {{> header }}

    <div class="container-fluid">

        <div class="row">
            <div class="col-sm-2 col-md-2 sidebar">
                {{> sidebar }}
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
                {{> yield }}


    {{> yield region='footer'}}


and a client/notfound.html:

<template name="notFound">
    <h1 class="page-header">Not found</h1>
        The page you are looking for cannot be found.

The various components like header and sidebar are defined in client/components/*.html as separate templates, with associated logic (e.g. Template.[name].helpers() and Template.[name].events) in client/js/*.js.

Other pages in client/*.html contain alternative pages. These are then navigated to using iron-router. The configuration is in client/js/startup.js. Here's an example that routes to two pages, forces login on anything but the home page, and has a 404 type handler:

/* global Collections */
"use strict";

Meteor.startup(function() {

    // any initialisation here

}); {

    // if `data` returns undefined/null, redirect to the `notFoundTemplate`.
        layoutTemplate: 'layout',
        notFoundTemplate: 'notFound'

    // force login
    Router.onBeforeAction(function(pause) {
        var self = this;

        if (!this.ready()) {

        var userId = Meteor.userId(),
            currentRoute = Router.current();

        // Redirect to home page if user is not logged in
        if(currentRoute && !== 'home' && !userId) {

    // Routes

    this.route('home', {
        path: '/',
        data: {}

    this.route('new', {
        path: '/new-entry',
        template: 'edit-entry',
        yieldTemplates: {
            configurationModal: {to: 'footer'}
        onRun: function() {
            // any preparation, e.g. update Session
        data: function() {
            // return a blank object
            return {foo: null, bar: null};

    this.route('entry', {
        path: '/entry/:_id',
        template: 'edit-entry',
        yieldTemplates: {
            configurationModal: {to: 'footer'}
        onRun: function() {
            // any preparation, e.g. update Session
        data: function() {
            // find the data object
            return Collections.MyModels.findOne(this.params._id);

Editor configuration