Meteor - Routing

meteor

https://guide.meteor.com/routing.html

Iron Router
FlowRouter

Do we need routing on the server side?

Not until we need to support our own API or server-side rendering. Meteor operates on the principle of data on the wire, where the server doesn’t think in terms of URLs or HTML pages. The client application communicates with the server over DDP. Typically as an application loads, it initializes a series of subscriptions which fetch the data required to render the application. As the user interacts with the application, different subscriptions may load, but there’s no technical need for URLs to be involved in this process - you could easily have a Meteor app where the URL never changes.

However, most of the user-facing features of URLs listed above are still relevant for typical Meteor applications. Since the server is not URL-driven, the URL just becomes a useful representation of the client-side state the user is currently looking at. However, unlike in a server-rendered application, it does not need to describe the entirety of the user’s current state; it simply needs to contain the parts that you want to be linkable. For example, the URL should contain any search filters applied on a page, but not necessarily the state of a dropdown menu or popup.

What package does the FlowRouter team recommend us to use for server-side routing?

meteorhacks:picker

Why does Meteor treat components as pages?

Notice that we called the component to be rendered Lists_show_page (rather than Lists_show). This indicates that this template is rendered directly by a Flow Router action and forms the ‘top’ of the rendering hierarchy for this URL.

The Lists_show_page template renders without arguments—it is this template’s responsibility to collect information from the current route, and then pass this information down into its child templates. Correspondingly the Lists_show_page template is very tied to the route that rendered it, and so it needs to be a smart component. See the article on UI/UX for more about smart and reusable components.

It makes sense for a “page” smart component like Lists_show_page to:

  1. Collect route information,
  2. Subscribe to relevant subscriptions,
  3. Fetch the data from those subscriptions, and
  4. Pass that data into a sub-component.

In this case, the HTML template for Lists_show_page will look very simple, with most of the logic in the JavaScript code:

<template name="Lists_show_page">
  {{#each listId in listIdArray}}
    {{> Lists_show (listArgs listId)}}
  {{else}}
    {{> App_notFound}}
  {{/each}}
</template>

(The {{#each listId in listIdArray}}} is an animation technique for page to page transitions).

Template.Lists_show_page.helpers({
  // We use #each on an array of one item so that the "list" template is
  // removed and a new copy is added when changing lists, which is
  // important for animation purposes.
  listIdArray() {
    const instance = Template.instance();
    const listId = instance.getListId();
    return Lists.findOne(listId) ? [listId] : [];
  },
  listArgs(listId) {
    const instance = Template.instance();
    return {
      todosReady: instance.subscriptionsReady(),
      // We pass `list` (which contains the full list, with all fields, as a function
      // because we want to control reactivity. When you check a todo item, the
      // `list.incompleteCount` changes. If we didn't do this the entire list would
      // re-render whenever you checked an item. By isolating the reactiviy on the list
      // to the area that cares about it, we stop it from happening.
      list() {
        return Lists.findOne(listId);
      },
      // By finding the list with only the `_id` field set, we don't create a dependency on the
      // `list.incompleteCount`, and avoid re-rendering the todos when it changes
      todos: Lists.findOne(listId, {fields: {_id: true}}).todos()
    };
  }
});

It’s the listShow component (a reusuable component) that actually handles the job of rendering the content of the page. As the page component is passing the arguments into the reusuable component, it is able to be quite mechanical and the concerns of talking to the router and rendering the page have been separated.

How can we share functionalities between templates?

We can easily share functionality between templates by wrapping them in a wrapper “layout” component which includes the behavior we want. https://guide.meteor.com/routing.html#route-rendering-logic

You can create wrapper components by using the “template as block helper” ability of Blaze (see the Blaze Article). Here’s how we could write an authorization template:

<template name="App_forceLoggedIn">
  {{#if currentUser}}
    {{> Template.contentBlock}}
  {{else}}
    Please log in see this page.
  {{/if}}
</template>

Once that template exists, we can simply wrap our Lists_show_page:

<template name="Lists_show_page">
  {{#App_forceLoggedIn}}
    {{#each listId in listIdArray}}
      {{> Lists_show (listArgs listId)}}
    {{else}}
      {{> App_notFound}}
    {{/each}}
  {{/App_forceLoggedIn}}
</template>

The main advantage of this approach is that it is immediately clear when viewing the Lists_show_page what behavior will occur when a user visits the page. Multiple behaviors of this type can be composed by wrapping a template in multiple wrappers, or creating a meta-wrapper that combines multiple wrapper templates.

How can we determine if a particular page is being used?

It’s common to want to know which pages of your app are most commonly visited, and where users are coming from. Here’s a simple setup that will get you URL tracking using Google Analytics. We’ll be using the okgrow:analytics package.

meteor add okgrow:analytics

Now, we need to configure the package with our Google Analytics key (the package also supports a large variety of other providers, check out the documentation on Atmosphere). Pass it in as part of Meteor settings:

{
  "public": {
    "analyticsSettings": {
      // Add your analytics tracking id's here
      "Google Analytics" : {"trackingId": "Your tracking ID"}
    }
  }
}

The analytics package hooks into Flow Router and records all of the page events for you. You may want to track non-page change related events (for instance publication subscription, or method calls) also. To do so you can use the custom event tracking functionality:

export const updateText = new ValidatedMethod({
  ...
  run({ todoId, newText }) {
    // We use `isClient` here because we only want to track
    // attempted method calls from the client, not server to
    // server method calls
    if (Meteor.isClient) {
      analytics.track('todos.updateText', { todoId, newText });
    }

    // ...
  }
});

To achieve a similar abstraction for subscriptions/publications, you may want to write a simple wrapper for Meteor.subscribe().

What are the two main cases for server-side routing?

As we’ve discussed, Meteor is a framework for client rendered applications, but this doesn’t always remove the requirement for server rendered routes. There are two main use cases for server-side routing.

  1. Server Routing for API access: Although Meteor allows you to write low-level connect handlers to create any kind of API you like on the server-side, if all you want to do is create a RESTful version of your Methods and Publications, you can often use the simple:rest package to do this easily. If you need more control, you can use the comprehensive nimble:restivus package to create more or less whatever you need in whatever ontology you require.
  2. Server Rendering: The Blaze UI library does not have support for server-side rendering, so it’s not possible to render your pages on the server if you use Blaze. However, the React UI library does. This means it is possible to render HTML on the server if you use React as your rendering framework. Although Flow Router can be used to render React components more or less as we’ve described above for Blaze, at the time of this writing Flow Router’s support for SSR is still experimental. However, it’s probably the best approach right now if you want to use SSR for Meteor.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License