From 0eb5045fe507c6ad35ad0b873b1a509823bc8a24 Mon Sep 17 00:00:00 2001
From: Tom Robinson <tlrobinson@gmail.com>
Date: Mon, 1 Aug 2016 02:03:18 -0700
Subject: [PATCH] =?UTF-8?q?Replace=20Angular=20routing=20with=20react-rout?=
 =?UTF-8?q?er,=20react-router-redux,=20redux-auth-wrapper.=20=F0=9F=8E=89?=
 =?UTF-8?q?=F0=9F=8E=89=F0=9F=8E=89=20FINALLY=20=F0=9F=8E=89=F0=9F=8E=89?=
 =?UTF-8?q?=F0=9F=8E=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 frontend/src/metabase/Routes.jsx              | 257 +++++++++------
 .../databases/containers/DatabaseEditApp.jsx  |   2 +-
 .../databases/containers/DatabaseListApp.jsx  |   8 +-
 .../components/ObjectActionSelect.jsx         |   9 +-
 .../components/database/MetadataTableList.jsx |   2 +-
 .../components/database/MetricsList.jsx       |   3 +-
 .../components/database/SegmentsList.jsx      |   3 +-
 .../containers/MetadataEditorApp.jsx          |  12 +-
 .../admin/datamodel/containers/MetricForm.jsx |   3 +-
 .../containers/RevisionHistoryApp.jsx         |   6 +-
 .../datamodel/containers/SegmentForm.jsx      |   3 +-
 .../src/metabase/admin/datamodel/selectors.js |  23 +-
 .../admin/people/components/AdminPeople.jsx   |   3 +-
 .../settings/containers/SettingsEditorApp.jsx |  16 +-
 .../src/metabase/admin/settings/selectors.js  |  10 +-
 .../src/metabase/admin/settings/settings.js   |  33 +-
 frontend/src/metabase/app.js                  | 312 ++++--------------
 frontend/src/metabase/auth/auth.js            |  21 +-
 .../metabase/auth/components/BackToLogin.jsx  |   5 +-
 .../src/metabase/auth/containers/LoginApp.jsx |  22 +-
 .../auth/containers/PasswordResetApp.jsx      |  16 +-
 .../src/metabase/components/Breadcrumbs.jsx   |   3 +-
 .../src/metabase/components/EmptyState.jsx    |   3 +-
 frontend/src/metabase/components/NotFound.jsx |   8 +-
 frontend/src/metabase/controllers.js          |  33 --
 .../dashboard/components/Dashboard.jsx        |   7 +-
 .../dashboard/components/DashboardHeader.jsx  |   4 +-
 .../DashCardCardParameterMapper.jsx           |   4 +-
 .../dashboard/containers/DashboardApp.jsx     |  26 +-
 .../dashboard/containers/ParameterWidget.jsx  |   2 +-
 frontend/src/metabase/dashboard/dashboard.js  |  32 +-
 frontend/src/metabase/dashboard/selectors.js  |   2 +-
 .../src/metabase/home/components/Activity.jsx |  23 +-
 .../home/components/ActivityStory.jsx         |   4 +-
 .../components/NewUserOnboardingModal.jsx     |   3 +-
 .../src/metabase/home/components/NextStep.jsx |   7 +-
 .../metabase/home/components/RecentViews.jsx  |   3 +-
 .../metabase/home/containers/HomepageApp.jsx  |   9 +-
 frontend/src/metabase/home/selectors.js       |   1 -
 frontend/src/metabase/lib/card.js             |   2 +
 frontend/src/metabase/lib/cookies.js          |  61 ++--
 .../metabase/nav/components/ProfileLink.jsx   |  21 +-
 .../nav/containers/DashboardsDropdown.jsx     |   5 +-
 .../src/metabase/nav/containers/Navbar.jsx    |  45 +--
 frontend/src/metabase/nav/selectors.js        |   2 +-
 .../metabase/pulse/components/PulseEdit.jsx   |   3 +-
 .../pulse/components/PulseListItem.jsx        |   7 +-
 .../pulse/components/SetupMessage.jsx         |   3 +-
 .../pulse/containers/PulseEditApp.jsx         |   2 +-
 .../pulse/containers/PulseListApp.jsx         |   2 +-
 frontend/src/metabase/pulse/selectors.js      |  14 +-
 .../query_builder/QueryVisualization.jsx      |   3 +-
 .../src/metabase/query_builder/actions.js     |  95 ++++--
 .../query_builder/containers/QueryBuilder.jsx |  10 +-
 .../src/metabase/query_builder/reducers.js    |   5 -
 frontend/src/metabase/reducers.js             |  65 ++++
 .../reference/containers/ReferenceApp.jsx     |  12 +-
 .../reference/containers/ReferenceEntity.jsx  |  20 +-
 .../containers/ReferenceEntityList.jsx        |  12 +-
 .../containers/ReferenceFieldsList.jsx        |  14 +-
 .../containers/ReferenceRevisionsList.jsx     |  16 +-
 frontend/src/metabase/reference/selectors.js  |  44 +--
 frontend/src/metabase/services.js             | 211 +-----------
 .../src/metabase/setup/components/Setup.jsx   |   3 +-
 frontend/src/metabase/store.js                |  59 ++++
 frontend/src/metabase/user.js                 |  14 +-
 frontend/src/metabase/user/actions.js         |   6 +-
 .../src/metabase/visualizations/Scalar.jsx    |   3 +-
 package.json                                  |  13 +-
 resources/frontend_client/index_template.html |   6 +-
 src/metabase/api/setup.clj                    |   4 +-
 71 files changed, 786 insertions(+), 939 deletions(-)
 create mode 100644 frontend/src/metabase/reducers.js
 create mode 100644 frontend/src/metabase/store.js

diff --git a/frontend/src/metabase/Routes.jsx b/frontend/src/metabase/Routes.jsx
index 24f7ad54e1c..5b55c9e6492 100644
--- a/frontend/src/metabase/Routes.jsx
+++ b/frontend/src/metabase/Routes.jsx
@@ -1,7 +1,6 @@
 import React, { Component, PropTypes } from "react";
 
-import { Route } from 'react-router';
-import { ReduxRouter } from 'redux-router';
+import { Route, IndexRoute, IndexRedirect, Redirect } from 'react-router';
 
 // auth containers
 import ForgotPasswordApp from "metabase/auth/containers/ForgotPasswordApp.jsx";
@@ -45,100 +44,170 @@ import ReferenceGettingStartedGuide from "metabase/reference/containers/Referenc
 
 import Navbar from "metabase/nav/containers/Navbar.jsx";
 
-export default class Routes extends Component {
-    // this lets us forward props we've injected from the Angular controller
-    _forwardProps(ComposedComponent, propNames) {
-        let forwarededProps = {};
-        for (const propName of propNames) {
-            forwarededProps[propName] = this.props[propName];
-        }
-        return (props) => <ComposedComponent {...props} {...forwarededProps} />;
-    }
+import { UserAuthWrapper } from 'redux-auth-wrapper';
+
+// START react-router-redux
+import { routerActions } from 'react-router-redux';
+const redirectAction = routerActions.replace;
+// END react-router-redux
+
+// START redux-router
+// import { push } from 'redux-router';
+// const redirectAction = ({ pathname, query }) => {
+//     console.log("REDIRECT", pathname, query);
+//     if (query.redirect) {
+//         return push(`${pathname}?next=${query.redirect}`)
+//     } else {
+//         return push(pathname)
+//     }
+// };
+// END redux-router
+
+// Create the wrapper that checks if user is authenticated.
+const UserIsAuthenticated = UserAuthWrapper({
+  // Select the field of the state with auth data
+  authSelector: state => state.currentUser,
+  redirectAction: redirectAction,
+  // Choose the url to redirect not authenticated users.
+  failureRedirectPath: '/auth/login',
+  wrapperDisplayName: 'UserIsAuthenticated'
+})
+
+// Do the same to create the wrapper that checks if user is NOT authenticated.
+const UserIsNotAuthenticated = UserAuthWrapper({
+  authSelector: state => state.currentUser,
+  redirectAction: redirectAction,
+  failureRedirectPath: '/',
+  // Choose what exactly you need to check in the selected field of the state
+  // (in the previous wrapper it checks by default).
+  predicate: currentUser => !currentUser,
+  wrapperDisplayName: 'UserIsNotAuthenticated'
+})
+
+import { connect } from "react-redux";
+import { push } from "react-router-redux";
+
+function FIXME_forwardOnChangeLocation(Component) {
+    return connect(null, { onChangeLocation: push })(Component)
+}
+
+const NotAuthenticated = UserIsNotAuthenticated(({ children }) => children);
+const Authenticated = UserIsAuthenticated(({ children }) => children);
 
+class App extends Component {
+    componentWillMount() {
+        console.log('will mount app')
+    }
     render() {
+        const { children, location } = this.props;
         return (
-            <ReduxRouter>
-                <Route component={({ children }) =>
-                    <div className="spread flex flex-column">
-                        <Navbar className="flex-no-shrink" onChangeLocation={this.props.onChangeLocation} />
-                        {children}
-                    </div>
-                }>
-                    <Route path="/" component={this._forwardProps(HomepageApp, ["onChangeLocation"])} />
-
-                    <Route path="/admin">
-                        <Route path="databases" component={DatabaseListApp} />
-                        <Route path="databases/create" component={this._forwardProps(DatabaseEditApp, ["onChangeLocation"])} />
-                        <Route path="databases/:databaseId" component={this._forwardProps(DatabaseEditApp, ["onChangeLocation"])} />
-
-                        <Route path="datamodel">
-                            <Route path="database" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
-                            <Route path="database/:databaseId" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
-                            <Route path="database/:databaseId/:mode" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
-                            <Route path="database/:databaseId/:mode/:tableId" component={this._forwardProps(MetadataEditorApp, ["onChangeLocation"])} />
-                            <Route path="metric/create" component={this._forwardProps(MetricApp, ["onChangeLocation"])} />
-                            <Route path="metric/:id" component={this._forwardProps(MetricApp, ["onChangeLocation"])} />
-                            <Route path="segment/create" component={this._forwardProps(SegmentApp, ["onChangeLocation"])} />
-                            <Route path="segment/:id" component={this._forwardProps(SegmentApp, ["onChangeLocation"])} />
-                            <Route path=":entity/:id/revisions" component={RevisionHistoryApp} />
-                        </Route>
-
-                        <Route path="people" component={this._forwardProps(AdminPeopleApp, ["onChangeLocation"])} />
-                        <Route path="settings" component={this._forwardProps(SettingsEditorApp, ["refreshSiteSettings"])} />
-                    </Route>
-
-                    <Route path="/reference" component={ReferenceApp}>
-                        <Route path="guide" component={ReferenceGettingStartedGuide} />
-                        <Route path="metrics" component={ReferenceEntityList} />
-                        <Route path="metrics/:metricId" component={ReferenceEntity} />
-                        <Route path="metrics/:metricId/questions" component={ReferenceEntityList} />
-                        <Route path="metrics/:metricId/revisions" component={ReferenceRevisionsList} />
-                        <Route path="segments" component={ReferenceEntityList} />
-                        <Route path="segments/:segmentId" component={ReferenceEntity} />
-                        <Route path="segments/:segmentId/fields" component={ReferenceFieldsList} />
-                        <Route path="segments/:segmentId/fields/:fieldId" component={ReferenceEntity} />
-                        <Route path="segments/:segmentId/questions" component={ReferenceEntityList} />
-                        <Route path="segments/:segmentId/revisions" component={ReferenceRevisionsList} />
-                        <Route path="databases" component={ReferenceEntityList} />
-                        <Route path="databases/:databaseId" component={ReferenceEntity} />
-                        <Route path="databases/:databaseId/tables" component={ReferenceEntityList} />
-                        <Route path="databases/:databaseId/tables/:tableId" component={ReferenceEntity} />
-                        <Route path="databases/:databaseId/tables/:tableId/fields" component={ReferenceFieldsList} />
-                        <Route path="databases/:databaseId/tables/:tableId/fields/:fieldId" component={ReferenceEntity} />
-                        <Route path="databases/:databaseId/tables/:tableId/questions" component={ReferenceEntityList} />
-                    </Route>
-
-                    <Route path="/auth">
-                        <Route path="forgot_password" component={ForgotPasswordApp} />
-                        <Route path="login" component={this._forwardProps(LoginApp, ["onChangeLocation"])} />
-                        <Route path="logout" component={this._forwardProps(LogoutApp, ["onChangeLocation"])} />
-                        <Route path="reset_password/:token" component={this._forwardProps(PasswordResetApp, ["onChangeLocation"])} />
-                        <Route path="google_no_mb_account" component={GoogleNoAccount} />
-                    </Route>
-
-                    <Route path="/dash/:dashboardId" component={this._forwardProps(DashboardApp, ["onChangeLocation"])} />
-
-                    <Route path="/pulse" component={this._forwardProps(PulseListApp, ["onChangeLocation"])} />
-                    <Route path="/pulse/create" component={this._forwardProps(PulseEditApp, ["onChangeLocation"])} />
-                    <Route path="/pulse/:pulseId" component={this._forwardProps(PulseEditApp, ["onChangeLocation"])} />
-
-                    <Route path="/card/:cardId" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
-                    <Route path="/q" component={this._forwardProps(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
-
-                    <Route path="/questions" component={EntityBrowser}>
-                        <Route path="edit/labels" component={EditLabels} />
-                        <Route path=":section" component={EntityList} />
-                        <Route path=":section/:slug" component={EntityList} />
-                    </Route>
-
-                    <Route path="/setup" component={SetupApp} />
-
-                    <Route path="/user/edit_current" component={UserSettingsApp} />
-
-                    <Route path="/unauthorized" component={Unauthorized} />
-                    <Route path="/*" component={NotFound} />
-                </Route>
-            </ReduxRouter>
-        );
+            <div className="spread flex flex-column">
+                <Navbar location={location} className="flex-no-shrink" />
+                {children}
+            </div>
+        )
     }
 }
+
+const Routes =
+    <Route component={App}>
+        {/* AUTH */}
+        <Route path="/auth">
+            <IndexRedirect to="/auth/login" />
+            <Route path="forgot_password" component={ForgotPasswordApp} />
+            <Route path="login" component={LoginApp} />
+            <Route path="logout" component={LogoutApp} />
+            <Route path="reset_password/:token" component={PasswordResetApp} />
+            <Route path="google_no_mb_account" component={GoogleNoAccount} />
+        </Route>
+
+        {/* SETUP */}
+        <Route path="/setup" component={SetupApp} />
+
+        {/* MAIN */}
+        <Route component={Authenticated}>
+            {/* HOME */}
+            <Route path="/" component={HomepageApp} />
+
+            {/* DASHBOARD */}
+            <Route path="/dash/:dashboardId" component={FIXME_forwardOnChangeLocation(DashboardApp)} />
+
+            {/* QUERY BUILDER */}
+            <Route path="/card/:cardId" component={FIXME_forwardOnChangeLocation(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
+            <Route path="/q" component={FIXME_forwardOnChangeLocation(QueryBuilder, ["onChangeLocation", "updateUrl"])} />
+
+            {/* QUESTIONS */}
+            <Route path="/questions" component={EntityBrowser}>
+                <Route path="edit/labels" component={EditLabels} />
+                <Route path=":section" component={EntityList} />
+                <Route path=":section/:slug" component={EntityList} />
+            </Route>
+
+            {/* REFERENCE */}
+            <Route path="/reference" component={ReferenceApp}>
+                <IndexRedirect to="/reference/guide" />
+                <Route path="guide" component={ReferenceGettingStartedGuide} />
+                <Route path="metrics" component={ReferenceEntityList} />
+                <Route path="metrics/:metricId" component={ReferenceEntity} />
+                <Route path="metrics/:metricId/questions" component={ReferenceEntityList} />
+                <Route path="metrics/:metricId/revisions" component={ReferenceRevisionsList} />
+                <Route path="segments" component={ReferenceEntityList} />
+                <Route path="segments/:segmentId" component={ReferenceEntity} />
+                <Route path="segments/:segmentId/fields" component={ReferenceFieldsList} />
+                <Route path="segments/:segmentId/fields/:fieldId" component={ReferenceEntity} />
+                <Route path="segments/:segmentId/questions" component={ReferenceEntityList} />
+                <Route path="segments/:segmentId/revisions" component={ReferenceRevisionsList} />
+                <Route path="databases" component={ReferenceEntityList} />
+                <Route path="databases/:databaseId" component={ReferenceEntity} />
+                <Route path="databases/:databaseId/tables" component={ReferenceEntityList} />
+                <Route path="databases/:databaseId/tables/:tableId" component={ReferenceEntity} />
+                <Route path="databases/:databaseId/tables/:tableId/fields" component={ReferenceFieldsList} />
+                <Route path="databases/:databaseId/tables/:tableId/fields/:fieldId" component={ReferenceEntity} />
+                <Route path="databases/:databaseId/tables/:tableId/questions" component={ReferenceEntityList} />
+            </Route>
+
+            {/* PULSE */}
+            <Route path="/pulse" component={FIXME_forwardOnChangeLocation(PulseListApp)} />
+            <Route path="/pulse/create" component={FIXME_forwardOnChangeLocation(PulseEditApp)} />
+            <Route path="/pulse/:pulseId" component={FIXME_forwardOnChangeLocation(PulseEditApp)} />
+
+            {/* USER */}
+            <Route path="/user/edit_current" component={UserSettingsApp} />
+        </Route>
+
+        {/* ADMIN */}
+        <Route path="/admin" component={Authenticated}>
+            <IndexRedirect to="/admin/settings" />
+            <Route path="databases" component={DatabaseListApp} />
+            <Route path="databases/create" component={FIXME_forwardOnChangeLocation(DatabaseEditApp)} />
+            <Route path="databases/:databaseId" component={FIXME_forwardOnChangeLocation(DatabaseEditApp)} />
+
+            <Route path="datamodel">
+                <Route path="database" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
+                <Route path="database/:databaseId" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
+                <Route path="database/:databaseId/:mode" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
+                <Route path="database/:databaseId/:mode/:tableId" component={FIXME_forwardOnChangeLocation(MetadataEditorApp)} />
+                <Route path="metric/create" component={FIXME_forwardOnChangeLocation(MetricApp)} />
+                <Route path="metric/:id" component={FIXME_forwardOnChangeLocation(MetricApp)} />
+                <Route path="segment/create" component={FIXME_forwardOnChangeLocation(SegmentApp)} />
+                <Route path="segment/:id" component={FIXME_forwardOnChangeLocation(SegmentApp)} />
+                <Route path=":entity/:id/revisions" component={RevisionHistoryApp} />
+            </Route>
+
+            <Route path="people" component={FIXME_forwardOnChangeLocation(AdminPeopleApp)} />
+
+            <Route path="settings" component={SettingsEditorApp} />
+            <Route path="settings/:section" component={SettingsEditorApp} />
+        </Route>
+
+        {/* MISC */}
+        <Route path="/unauthorized" component={Unauthorized} />
+        <Route path="/*" component={NotFound} />
+
+        {/* LEGACY */}
+        <Redirect from="/card" to="/questions" />
+        <Redirect from="/card/:cardId/:serializedCard" to="/questions/:cardId#:serializedCard" />
+        <Redirect from="/q/:serializedCard" to="/q#:serializedCard" />
+    </Route>
+
+export default Routes;
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
index 946268410de..3982bac77fb 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseEditApp.jsx
@@ -17,7 +17,7 @@ import * as databaseActions from "../database";
 
 const mapStateToProps = (state, props) => {
     return {
-        databaseId:       state.router && state.router.params && state.router.params.databaseId,
+        databaseId:       props.params.databaseId,
         database:         getEditingDatabase(state),
         formState:        getFormState(state),
         onChangeLocation: props.onChangeLocation
diff --git a/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx b/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
index d171b25d173..c8ba614bf04 100644
--- a/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
+++ b/frontend/src/metabase/admin/databases/containers/DatabaseListApp.jsx
@@ -1,5 +1,7 @@
 import React, { Component, PropTypes } from "react";
 import { connect } from "react-redux";
+import { Link } from "react-router";
+
 import cx from "classnames";
 import MetabaseSettings from "metabase/lib/settings";
 import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
@@ -17,7 +19,7 @@ import * as databaseActions from "../database";
 
 const mapStateToProps = (state, props) => {
     return {
-        created:              state.router && state.router.params && state.router.params.created,
+        created:              props.params.created,
         databases:            getDatabasesSorted(state),
         hasSampleDataset:     hasSampleDataset(state),
         engines:              MetabaseSettings.get('engines')
@@ -46,7 +48,7 @@ export default class DatabaseList extends Component {
         return (
             <div className="wrapper">
                 <section className="PageHeader px2 clearfix">
-                    <a className="Button Button--primary float-right" href="/admin/databases/create">Add database</a>
+                    <Link to="/admin/databases/create" className="Button Button--primary float-right">Add database</Link>
                     <h2 className="PageTitle">Databases</h2>
                 </section>
                 <section>
@@ -63,7 +65,7 @@ export default class DatabaseList extends Component {
                                 databases.map(database =>
                                     <tr key={database.id}>
                                         <td>
-                                            <a className="text-bold link" href={"/admin/databases/"+database.id}>{database.name}</a>
+                                            <Link to={"/admin/databases/"+database.id} className="text-bold link">{database.name}</Link>
                                         </td>
                                         <td>
                                             {engines && engines[database.engine] ? engines[database.engine]['driver-name'] : database.engine}
diff --git a/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx b/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
index 945e45c5f5a..9910e1a11a7 100644
--- a/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/ObjectActionSelect.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx";
@@ -30,14 +31,14 @@ export default class ObjectActionsSelect extends Component {
                 >
                     <ul className="UserActionsSelect">
                         <li>
-                            <a data-metabase-event={"Data Model;"+objectType+" Edit Page"}href={"/admin/datamodel/" + objectType + "/" + object.id} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
+                            <Link to={"/admin/datamodel/" + objectType + "/" + object.id} data-metabase-event={"Data Model;"+objectType+" Edit Page"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
                                 Edit {capitalize(objectType)}
-                            </a>
+                            </Link>
                         </li>
                         <li>
-                            <a data-metabase-event={"Data Model;"+objectType+" History"} href={"/admin/datamodel/" + objectType + "/" + object.id + "/revisions"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
+                            <Link to={"/admin/datamodel/" + objectType + "/" + object.id + "/revisions"} data-metabase-event={"Data Model;"+objectType+" History"} className="py1 px2 block bg-brand-hover text-white-hover no-decoration cursor-pointer">
                                 Revision History
-                            </a>
+                            </Link>
                         </li>
                         <li className="mt1 border-top">
                             <ModalWithTrigger
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx
index f7932ad14f9..c33d7bfe0eb 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataTableList.jsx
@@ -43,7 +43,7 @@ export default class MetadataTableList extends Component {
             _.each(tables, (table) => {
                 var row = (
                     <li key={table.id}>
-                        <a href="#" className={cx("AdminList-item flex align-center no-decoration", { selected: this.props.tableId === table.id })} onClick={this.props.selectTable.bind(null, table)}>
+                        <a className={cx("AdminList-item flex align-center no-decoration", { selected: this.props.tableId === table.id })} onClick={this.props.selectTable.bind(null, table)}>
                             {table.display_name}
                             <ProgressBar className="ProgressBar ProgressBar--mini flex-align-right" percentage={table.metadataStrength} />
                         </a>
diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
index 06612a95f6c..a8e5b6d2097 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/MetricsList.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import MetricItem from "./MetricItem.jsx";
 
@@ -18,7 +19,7 @@ export default class MetricsList extends Component {
             <div className="my3">
                 <div className="flex mb1">
                     <h2 className="px1 text-green">Metrics</h2>
-                    <a data-metabase-event="Data Model;Add Metric Page" className="flex-align-right float-right text-bold text-brand no-decoration" href={"/admin/datamodel/metric/create?table="+tableMetadata.id}>+ Add a Metric</a>
+                    <Link to={"/admin/datamodel/metric/create?table="+tableMetadata.id} data-metabase-event="Data Model;Add Metric Page" className="flex-align-right float-right text-bold text-brand no-decoration">+ Add a Metric</Link>
                 </div>
                 <table className="AdminTable">
                     <thead>
diff --git a/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
index 9d977347755..c53d38e100c 100644
--- a/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/database/SegmentsList.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import SegmentItem from "./SegmentItem.jsx";
 
@@ -18,7 +19,7 @@ export default class SegmentsList extends Component {
             <div className="my3">
                 <div className="flex mb1">
                     <h2 className="px1 text-purple">Segments</h2>
-                    <a data-metabase-event="Data Model;Add Segment Page" className="flex-align-right float-right text-bold text-brand no-decoration" href={"/admin/datamodel/segment/create?table="+tableMetadata.id}>+ Add a Segment</a>
+                    <Link to={"/admin/datamodel/segment/create?table="+tableMetadata.id} data-metabase-event="Data Model;Add Segment Page" className="flex-align-right float-right text-bold text-brand no-decoration">+ Add a Segment</Link>
                 </div>
                 <table className="AdminTable">
                     <thead>
diff --git a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
index 2961405399f..bee7bd4b21f 100644
--- a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
@@ -21,13 +21,13 @@ import * as metadataActions from "../metadata";
 
 const mapStateToProps = (state, props) => {
     return {
-        databaseId:           state.router && state.router.params && parseInt(state.router.params.databaseId),
-        tableId:              state.router && state.router.params && parseInt(state.router.params.tableId),
+        databaseId:           parseInt(props.params.databaseId),
+        tableId:              parseInt(props.params.tableId),
         onChangeLocation:     props.onChangeLocation,
-        databases:            getDatabases(state),
-        idfields:             getDatabaseIdfields(state),
-        databaseMetadata:     getEditingDatabaseWithTableMetadataStrengths(state),
-        editingTable:         getEditingTable(state)
+        databases:            getDatabases(state, props),
+        idfields:             getDatabaseIdfields(state, props),
+        databaseMetadata:     getEditingDatabaseWithTableMetadataStrengths(state, props),
+        editingTable:         getEditingTable(state, props)
     }
 }
 
diff --git a/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx b/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
index 185af29564d..2913ed122cd 100644
--- a/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import FormLabel from "../components/FormLabel.jsx";
 import FormInput from "../components/FormInput.jsx";
@@ -53,7 +54,7 @@ export default class MetricForm extends Component {
         return (
             <div>
                 <button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button>
-                <a className="Button Button--borderless mx1" href={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id}>Cancel</a>
+                <Link to={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id} className="Button Button--borderless mx1">Cancel</Link>
             </div>
         )
     }
diff --git a/frontend/src/metabase/admin/datamodel/containers/RevisionHistoryApp.jsx b/frontend/src/metabase/admin/datamodel/containers/RevisionHistoryApp.jsx
index 76e9f944f95..80dab1ccb99 100644
--- a/frontend/src/metabase/admin/datamodel/containers/RevisionHistoryApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/RevisionHistoryApp.jsx
@@ -9,9 +9,9 @@ import { connect } from "react-redux";
 
 const mapStateToProps = (state, props) => {
     return {
-        ...revisionHistorySelectors(state),
-        entity: state.router && state.router.params && state.router.params.entity,
-        id:     state.router && state.router.params && state.router.params.id
+        ...revisionHistorySelectors(state, props),
+        entity: props.params.entity,
+        id:     props.params.id
     }
 }
 
diff --git a/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx b/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
index a0e41a11ffa..09f324407fa 100644
--- a/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import FormLabel from "../components/FormLabel.jsx";
 import FormInput from "../components/FormInput.jsx";
@@ -54,7 +55,7 @@ export default class SegmentForm extends Component {
         return (
             <div>
                 <button className={cx("Button", { "Button--primary": !invalid, "disabled": invalid })} onClick={handleSubmit}>Save changes</button>
-                <a className="Button Button--borderless mx1" href={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id}>Cancel</a>
+                <Link to={"/admin/datamodel/database/" + tableMetadata.db_id + "/table/" + tableMetadata.id} className="Button Button--borderless mx1">Cancel</Link>
             </div>
         )
     }
diff --git a/frontend/src/metabase/admin/datamodel/selectors.js b/frontend/src/metabase/admin/datamodel/selectors.js
index 189c0caab3b..8feb9612b04 100644
--- a/frontend/src/metabase/admin/datamodel/selectors.js
+++ b/frontend/src/metabase/admin/datamodel/selectors.js
@@ -3,17 +3,17 @@ import { createSelector } from 'reselect';
 import { computeMetadataStrength } from "metabase/lib/schema_metadata";
 
 
-const segmentsSelector         = state => state.datamodel.segments;
-const metricsSelector          = state => state.datamodel.metrics;
+const segmentsSelector         = (state, props) => state.datamodel.segments;
+const metricsSelector          = (state, props) => state.datamodel.metrics;
 
-const tableMetadataSelector    = state => state.datamodel.tableMetadata;
-const previewSummarySelector   = state => state.datamodel.previewSummary;
-const revisionObjectSelector   = state => state.datamodel.revisionObject;
+const tableMetadataSelector    = (state, props) => state.datamodel.tableMetadata;
+const previewSummarySelector   = (state, props) => state.datamodel.previewSummary;
+const revisionObjectSelector   = (state, props) => state.datamodel.revisionObject;
 
-const idSelector               = state => state.router.params.id == null ? null : parseInt(state.router.params.id);
-const tableIdSelector          = state => state.router.location.query.table == null ? null : parseInt(state.router.location.query.table);
+const idSelector               = (state, props) => props.params.id == null ? null : parseInt(props.params.id);
+const tableIdSelector          = (state, props) => props.location.query.table == null ? null : parseInt(props.location.query.table);
 
-const userSelector             = state => state.currentUser;
+const userSelector             = (state, props) => state.currentUser;
 
 export const segmentEditSelectors = createSelector(
     segmentsSelector,
@@ -73,9 +73,9 @@ export const revisionHistorySelectors = createSelector(
 );
 
 
-export const getDatabases             = state => state.datamodel.databases;
-export const getDatabaseIdfields      = state => state.datamodel.idfields;
-export const getEditingTable          = state => state.datamodel.editingTable;
+export const getDatabases             = (state, props) => state.datamodel.databases;
+export const getDatabaseIdfields      = (state, props) => state.datamodel.idfields;
+export const getEditingTable          = (state, props) => state.datamodel.editingTable;
 
 
 export const getEditingDatabaseWithTableMetadataStrengths = createSelector(
@@ -93,4 +93,3 @@ export const getEditingDatabaseWithTableMetadataStrengths = createSelector(
         return database;
     }
 );
-
diff --git a/frontend/src/metabase/admin/people/components/AdminPeople.jsx b/frontend/src/metabase/admin/people/components/AdminPeople.jsx
index 237533ea7f0..3d7089be92a 100644
--- a/frontend/src/metabase/admin/people/components/AdminPeople.jsx
+++ b/frontend/src/metabase/admin/people/components/AdminPeople.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 import _ from "underscore";
 
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
@@ -183,7 +184,7 @@ export default class AdminPeople extends Component {
 
                             <PasswordReveal password={user.password} />
 
-                            <div style={{paddingLeft: "5em", paddingRight: "5em"}} className="pt4 text-centered">If you want to be able to send email invites, just go to the <a className="link text-bold" href="/admin/settings/?section=Email">Email Settings</a> page.</div>
+                            <div style={{paddingLeft: "5em", paddingRight: "5em"}} className="pt4 text-centered">If you want to be able to send email invites, just go to the <Link to="/admin/settings/email" className="link text-bold">Email Settings</Link> page.</div>
                         </div>
 
                         <div className="Form-actions">
diff --git a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
index 393b26ff60c..d9610291ad7 100644
--- a/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
+++ b/frontend/src/metabase/admin/settings/containers/SettingsEditorApp.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 import { connect } from "react-redux";
 import MetabaseAnalytics from "metabase/lib/analytics";
 
@@ -24,11 +25,10 @@ import * as settingsActions from "../settings";
 
 const mapStateToProps = (state, props) => {
     return {
-        refreshSiteSettings: props.refreshSiteSettings,
-        settings:            getSettings(state),
-        sections:            getSections(state),
-        activeSection:       getActiveSection(state),
-        newVersionAvailable: getNewVersionAvailable(state)
+        settings:            getSettings(state, props),
+        sections:            getSections(state, props),
+        activeSection:       getActiveSection(state, props),
+        newVersionAvailable: getNewVersionAvailable(state, props)
     }
 }
 
@@ -54,7 +54,7 @@ export default class SettingsEditorApp extends Component {
     };
 
     componentWillMount() {
-        this.props.initializeSettings(this.props.refreshSiteSettings);
+        this.props.initializeSettings();
     }
 
     updateSetting(setting, value) {
@@ -160,10 +160,10 @@ export default class SettingsEditorApp extends Component {
 
             return (
                 <li key={section.name}>
-                    <a href={"/admin/settings/?section=" + section.name} className={classes}>
+                    <Link to={"/admin/settings/" + section.slug}  className={classes}>
                         <span>{section.name}</span>
                         {newVersionIndicator}
-                    </a>
+                    </Link>
                 </li>
             );
         });
diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js
index b064ccb015a..293f97b075a 100644
--- a/frontend/src/metabase/admin/settings/selectors.js
+++ b/frontend/src/metabase/admin/settings/selectors.js
@@ -2,6 +2,7 @@ import _ from "underscore";
 import { createSelector } from "reselect";
 import MetabaseSettings from "metabase/lib/settings";
 
+import { slugify } from "metabase/lib/formatting";
 
 const SECTIONS = [
     {
@@ -149,6 +150,9 @@ const SECTIONS = [
         ]
     }
 ];
+for (const section of SECTIONS) {
+    section.slug = slugify(section.name);
+}
 
 export const getSettings = state => state.settings.settings;
 
@@ -183,14 +187,14 @@ export const getSections = createSelector(
     }
 );
 
-export const getActiveSectionName = (state) => state.router && state.router.location && state.router.location.query.section
+export const getActiveSectionName = (state, props) => props.params.section
 
 export const getActiveSection = createSelector(
     getActiveSectionName,
     getSections,
-    (section = "Setup", sections) => {
+    (section = "setup", sections) => {
         if (sections) {
-            return _.findWhere(sections, {name: section});
+            return _.findWhere(sections, { slug: section });
         } else {
             return null;
         }
diff --git a/frontend/src/metabase/admin/settings/settings.js b/frontend/src/metabase/admin/settings/settings.js
index 48987fce6e7..49ad04b4714 100644
--- a/frontend/src/metabase/admin/settings/settings.js
+++ b/frontend/src/metabase/admin/settings/settings.js
@@ -1,12 +1,20 @@
 
 import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
 
+import MetabaseSettings from 'metabase/lib/settings';
+
+import _ from "underscore";
 
 // resource wrappers
 const SettingsApi = new AngularResourceProxy("Settings", ["list", "put"]);
 const EmailApi = new AngularResourceProxy("Email", ["updateSettings", "sendTest"]);
 const SlackApi = new AngularResourceProxy("Slack", ["updateSettings"]);
+const SessionApi = new AngularResourceProxy("Session", ["properties"]);
 
+async function refreshSiteSettings() {
+    const settings = await SessionApi.properties();
+    MetabaseSettings.setAll(_.omit(settings, (value, key) => key.indexOf('$') === 0));
+}
 
 async function loadSettings() {
     try {
@@ -22,14 +30,10 @@ async function loadSettings() {
 }
 
 // initializeSettings
-export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", function(refreshSiteSettings) {
+export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", function() {
     return async function(dispatch, getState) {
         try {
-            let settings = await loadSettings();
-            return {
-                settings,
-                refreshSiteSettings
-            }
+            return await loadSettings();
         } catch(error) {
             console.log("error fetching settings", error);
             throw error;
@@ -40,8 +44,6 @@ export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", funct
 // updateSetting
 export const updateSetting = createThunkAction("UPDATE_SETTING", function(setting) {
     return async function(dispatch, getState) {
-        const { settings: { refreshSiteSettings } } = getState();
-
         try {
             await SettingsApi.put({ key: setting.key }, setting);
             refreshSiteSettings();
@@ -56,8 +58,6 @@ export const updateSetting = createThunkAction("UPDATE_SETTING", function(settin
 // updateEmailSettings
 export const updateEmailSettings = createThunkAction("UPDATE_EMAIL_SETTINGS", function(settings) {
     return async function(dispatch, getState) {
-        const { settings: { refreshSiteSettings } } = getState();
-
         try {
             await EmailApi.updateSettings(settings);
             refreshSiteSettings();
@@ -84,8 +84,6 @@ export const sendTestEmail = createThunkAction("SEND_TEST_EMAIL", function() {
 // updateSlackSettings
 export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", function(settings) {
     return async function(dispatch, getState) {
-        const { settings: { refreshSiteSettings } } = getState();
-
         try {
             await SlackApi.updateSettings(settings);
             refreshSiteSettings();
@@ -97,22 +95,15 @@ export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", fu
     };
 });
 
-
 // reducers
 
-// this is a backwards compatibility thing with angular to allow programmatic route changes.  remove/change this when going to ReduxRouter
-const refreshSiteSettings = handleActions({
-    ["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload ? payload.refreshSiteSettings : state }
-}, () => null);
-
 const settings = handleActions({
-    ["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload ? payload.settings : state },
+    ["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload },
     ["UPDATE_SETTING"]: { next: (state, { payload }) => payload },
     ["UPDATE_EMAIL_SETTINGS"]: { next: (state, { payload }) => payload },
     ["UPDATE_SLACK_SETTINGS"]: { next: (state, { payload }) => payload }
 }, []);
 
 export default combineReducers({
-    settings,
-    refreshSiteSettings
+    settings
 });
diff --git a/frontend/src/metabase/app.js b/frontend/src/metabase/app.js
index b7ef264975b..bb9e414c02e 100644
--- a/frontend/src/metabase/app.js
+++ b/frontend/src/metabase/app.js
@@ -2,263 +2,77 @@
 
 import 'babel-polyfill';
 
+import { registerAnalyticsClickListener } from "metabase/lib/analytics";
+
 // angular:
 import 'angular';
-import 'angular-cookies';
 import 'angular-resource';
-import 'angular-route';
-
-// angular 3rd-party:
 import 'angular-cookie';
-import 'angular-http-auth';
-
-import "./controllers";
 import "./services";
 
-import React from "react";
-import ReactDOM from "react-dom";
-
-import { Provider } from 'react-redux';
-
-import { combineReducers } from "redux";
-import { reducer as form } from "redux-form";
-import { reduxReactRouter, routerStateReducer } from "redux-router";
-
-import Routes from "./Routes.jsx";
-
-import auth from "metabase/auth/auth";
-
-/* ducks */
-import metadata from "metabase/redux/metadata";
-import requests from "metabase/redux/requests";
-
-/* admin */
-import settings from "metabase/admin/settings/settings";
-import * as people from "metabase/admin/people/reducers";
-import databases from "metabase/admin/databases/database";
-import datamodel from "metabase/admin/datamodel/metadata";
-
-/* dashboards */
-import dashboard from "metabase/dashboard/dashboard";
-import * as home from "metabase/home/reducers";
-
-/* questions / query builder */
-import questions from "metabase/questions/questions";
-import labels from "metabase/questions/labels";
-import undo from "metabase/questions/undo";
-import * as qb from "metabase/query_builder/reducers";
-
-/* data reference */
-import reference from "metabase/reference/reference";
-
-/* pulses */
-import * as pulse from "metabase/pulse/reducers";
-
-/* setup */
-import * as setup from "metabase/setup/reducers";
-
-/* user */
-import * as user from "metabase/user/reducers";
-import { currentUser, setUser } from "metabase/user";
-
-import { registerAnalyticsClickListener } from "metabase/lib/analytics";
-import { serializeCardForUrl, cleanCopyCard, urlForCardState } from "metabase/lib/card";
-import { createStoreWithAngularScope } from "metabase/lib/redux";
-
-const reducers = combineReducers({
-    form,
-    router: routerStateReducer,
-
-    // global reducers
-    auth,
-    currentUser,
-    metadata,
-    requests,
-
-    // main app reducers
-    dashboard,
-    home: combineReducers(home),
-    labels,
-    pulse: combineReducers(pulse),
-    qb: combineReducers(qb),
-    questions,
-    reference,
-    setup: combineReducers(setup),
-    undo,
-    user: combineReducers(user),
-
-    // admin reducers
-    databases,
-    datamodel: datamodel,
-    people: combineReducers(people),
-    settings
-});
-
-// Declare app level module which depends on filters, and services
-angular.module('metabase', [
-    'ngRoute',
-    'ngCookies',
-    'metabase.controllers',
-])
-.run(["AppState", function(AppState) {
-    // initialize app state
-    AppState.init();
-
-    // start our analytics click listener
+angular
+.module('metabase', ['ngCookies', 'metabase.controllers'])
+.run([function() {
     registerAnalyticsClickListener();
 }])
-.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
-    $locationProvider.html5Mode({
-        enabled: true,
-        requireBase: false
-    });
-
-    const route = {
-        template: '<div id="main" />',
-        controller: 'AppController',
-        resolve: {
-            appState: ["AppState", function(AppState) {
-                return AppState.init();
-            }]
-        }
-    };
-
-    $routeProvider.when('/admin/', { redirectTo: () => ('/admin/settings') });
-    $routeProvider.when('/auth/', { redirectTo: () => ('/auth/login') });
-    $routeProvider.when('/card/', { redirectTo: () => ("/questions/all") });
-    $routeProvider.when('/card/:cardId/:serializedCard', { redirectTo: (routeParams) => ("/card/"+routeParams.cardId+"#"+routeParams.serializedCard) });
-    $routeProvider.when('/q/:serializedCard', { redirectTo: (routeParams) => ("/q#"+routeParams.serializedCard) });
-
-    $routeProvider.otherwise(route);
-}])
-.controller('AppController', ['$scope', '$location', '$route', '$rootScope', '$timeout', 'ipCookie', 'AppState',
-    function($scope, $location, $route, $rootScope, $timeout, ipCookie, AppState) {
-        const props = {
-            onChangeLocation(url) {
-                $scope.$apply(() => $location.url(url));
-            },
-            refreshSiteSettings() {
-                $scope.$apply(() => AppState.refreshSiteSettings());
-            },
-            updateUrl: (card, isDirty=false, replaceState=false) => {
-                if (!card) {
-                    return;
-                }
-                var copy = cleanCopyCard(card);
-                var newState = {
-                    card: copy,
-                    cardId: copy.id,
-                    serializedCard: serializeCardForUrl(copy)
-                };
-
-                if (angular.equals(window.history.state, newState)) {
-                    return;
-                }
-
-                var url = urlForCardState(newState, isDirty);
-
-                // if the serialized card is identical replace the previous state instead of adding a new one
-                // e.x. when saving a new card we want to replace the state and URL with one with the new card ID
-                replaceState = replaceState || (window.history.state && window.history.state.serializedCard === newState.serializedCard);
-
-                // ensure the digest cycle is run, otherwise pending location changes will prevent navigation away from query builder on the first click
-                $scope.$apply(() => {
-                    // prevents infinite digest loop
-                    // https://stackoverflow.com/questions/22914228/successfully-call-history-pushstate-from-angular-without-inifinite-digest
-                    $location.url(url);
-                    $location.replace();
-                    if (replaceState) {
-                        window.history.replaceState(newState, null, $location.absUrl());
-                    } else {
-                        window.history.pushState(newState, null, $location.absUrl());
-                    }
-                });
-            }
-        };
-
-        const store = createStoreWithAngularScope($scope, $location, reducers, {currentUser: AppState.model.currentUser});
-
-        const element = document.getElementById("main");
-
-        ReactDOM.render(
-            <Provider store={store}>
-                <Routes {...props} />
-            </Provider>,
-            element
-        );
 
-        $scope.$on("$destroy", function() {
-            ReactDOM.unmountComponentAtNode(element);
-        });
+angular
+.module('metabase.controllers', ['metabase.services'])
+.controller('Metabase', [function() {
+}]);
 
-        // ANGULAR_HACKâ„¢: this seems like the easiest way to keep the redux store up to date with the currentUser :-/
-        let userSyncTimer = setInterval(() => {
-            if (store.getState().currentUser !== AppState.model.currentUser) {
-                store.dispatch(setUser(AppState.model.currentUser));
-            }
-        }, 250);
-        $scope.$on("$destroy", () => clearInterval(userSyncTimer));
-
-        // HACK: prevent reloading controllers as the URL changes
-        let route = $route.current;
-        $scope.$on('$locationChangeSuccess', function (event) {
-            $route.current = route;
-        });
-    }
-])
-
-
-// async function refreshCurrentUser() {
-//     let response = await fetch("/api/user/current", { credentials: 'same-origin' });
-//     if (response.status === 200) {
-//         return await response.json();
-//     }
-// }
-
-
-// This is the entry point for our Redux application which is fired once on page load.
-// We attempt to:
-//   1. Identify the currently authenticated user, if possible
-//   2. Create the application Redux store
-//   3. Render our React/Redux application using a single Redux `Provider`
-// window.onload = async function() {
-//     // refresh site settings
-
-//     // fetch current user
-//     let user = await refreshCurrentUser();
-
-//     // initialize redux store
-//     // NOTE: we want to initialize the store with the active user because it makes lots of other initialization steps simpler
-//     let store = createMetabaseStore(reducers, {currentUser: user});
-
-//     // route change listener
-//     // set app context (for navbar)
-//     // guard admin urls and redirect to auth pages
-
-//     // events fired
-//     // appstate:user - currentUser changed
-//     // appstate:site-settings - changes made to current app settings
-//     // appstate:context-changed - app section changed (only used by navbar?)
-
-//     // listeners
-//     // $locationChangeSuccess - analytics route tracking
-//     // $routeChangeSuccess - route protection logic (updates context and redirects urls based on user perms)
-//     // appstate:login - refresh the currentUser
-//     // appstate:logout - null the currentUser and make sure cookie is cleared and session deleted
-//     // appstate:site-settings - if GA setting changed then update analytics appropriately
-//     // event:auth-loginRequired - (fired by angular service middleware) lets us know an api call returned a 401
-//     // event:auth-forbidden - (fired by angular service middleware) lets us know an api call returned a 403
-
-//     // start analytics
+import Routes from "./Routes.jsx";
 
-//     let reduxApp = document.getElementById("redux-app");
-//     render(
-//         <Provider store={store}>
-//             <div className="full full-height">
-//                 <div className="Nav"><Navbar /></div>
-//                 <main className="relative full-height z1"><Routes /></main>
-//             </div>
-//         </Provider>,
-//         reduxApp
-//     );
-// }
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Provider } from 'react-redux'
+import { getStore } from './store'
+
+// START react-router-redux
+import { Router, browserHistory } from "react-router";
+import { syncHistoryWithStore } from 'react-router-redux'
+// END react-router-redux
+
+// START redux-router
+// import { ReduxRouter } from "redux-router";
+// END redux-router
+
+import { refreshCurrentUser } from "./user";
+
+async function init() {
+    // const user = await getCurrentUser();
+
+    // START react-router-redux
+    const store = getStore(browserHistory);
+
+    await store.dispatch(refreshCurrentUser());
+
+    const history = syncHistoryWithStore(browserHistory, store);
+    ReactDOM.render(
+        <Provider store={store}>
+          <Router history={history}>
+            {Routes}
+          </Router>
+        </Provider>,
+      document.getElementById('root')
+    )
+    // END react-router-redux
+
+    // START redux-router
+    // const store = getStore(Routes);
+    // ReactDOM.render(
+    //     <Provider store={store}>
+    //       <ReduxRouter>
+    //         {Routes}
+    //       </ReduxRouter>
+    //     </Provider>,
+    //   document.getElementById('root')
+    // )
+    // END redux-router
+}
+
+if (document.readyState != 'loading') {
+    init();
+} else {
+    document.addEventListener('DOMContentLoaded', init);
+}
diff --git a/frontend/src/metabase/auth/auth.js b/frontend/src/metabase/auth/auth.js
index 3af4c586eda..bcf5b8114d7 100644
--- a/frontend/src/metabase/auth/auth.js
+++ b/frontend/src/metabase/auth/auth.js
@@ -1,19 +1,22 @@
 
 import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
 
+import { push } from "react-router-redux";
+
 import MetabaseCookies from "metabase/lib/cookies";
 import MetabaseUtils from "metabase/lib/utils";
 import MetabaseAnalytics from "metabase/lib/analytics";
 
 import { clearGoogleAuthCredentials } from "metabase/lib/auth";
 
+import { refreshCurrentUser } from "metabase/user";
 
 // resource wrappers
 const SessionApi = new AngularResourceProxy("Session", ["create", "createWithGoogleAuth", "delete", "reset_password"]);
 
 
 // login
-export const login = createThunkAction("AUTH_LOGIN", function(credentials, onChangeLocation) {
+export const login = createThunkAction("AUTH_LOGIN", function(credentials) {
     return async function(dispatch, getState) {
 
         if (!MetabaseUtils.validEmail(credentials.email)) {
@@ -29,7 +32,9 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
             MetabaseAnalytics.trackEvent('Auth', 'Login');
             // TODO: redirect after login (carry user to intended destination)
             // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
-            setTimeout(() => onChangeLocation("/"), 300);
+            // setTimeout(() => dispatch(push("/")), 300);
+            await dispatch(refreshCurrentUser());
+            dispatch(push("/"));
 
         } catch (error) {
             return error;
@@ -39,7 +44,7 @@ export const login = createThunkAction("AUTH_LOGIN", function(credentials, onCha
 
 
 // login Google
-export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser, onChangeLocation) {
+export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googleUser) {
     return async function(dispatch, getState) {
         try {
             let newSession = await SessionApi.createWithGoogleAuth({
@@ -53,13 +58,13 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl
 
             // TODO: redirect after login (carry user to intended destination)
             // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
-            setTimeout(() => onChangeLocation("/"), 300);
+            setTimeout(() => dispatch(push("/")), 300);
 
         } catch (error) {
             clearGoogleAuthCredentials();
             // If we see a 428 ("Precondition Required") that means we need to show the "No Metabase account exists for this Google Account" page
             if (error.status === 428) {
-                onChangeLocation('/auth/google_no_mb_account');
+                dispatch(push("/auth/google_no_mb_account"));
             } else {
                 return error;
             }
@@ -68,7 +73,7 @@ export const loginGoogle = createThunkAction("AUTH_LOGIN_GOOGLE", function(googl
 });
 
 // logout
-export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation) {
+export const logout = createThunkAction("AUTH_LOGOUT", function() {
     return async function(dispatch, getState) {
         // TODO: as part of a logout we want to clear out any saved state that we have about anything
 
@@ -79,12 +84,12 @@ export const logout = createThunkAction("AUTH_LOGOUT", function(onChangeLocation
         }
         MetabaseAnalytics.trackEvent('Auth', 'Logout');
 
-        setTimeout(() => onChangeLocation("/auth/login"), 300);
+        setTimeout(() => dispatch(push("/auth/login")), 300);
     };
 });
 
 // passwordReset
-export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(token, credentials, onChangeLocation) {
+export const passwordReset = createThunkAction("AUTH_PASSWORD_RESET", function(token, credentials) {
     return async function(dispatch, getState) {
 
         if (credentials.password !== credentials.password2) {
diff --git a/frontend/src/metabase/auth/components/BackToLogin.jsx b/frontend/src/metabase/auth/components/BackToLogin.jsx
index 4d602083b2b..4ded6c192a1 100644
--- a/frontend/src/metabase/auth/components/BackToLogin.jsx
+++ b/frontend/src/metabase/auth/components/BackToLogin.jsx
@@ -1,6 +1,7 @@
-import React from 'react'
+import React from 'react';
+import { Link } from "react-router";
 
 const BackToLogin = () =>
-    <a className="link block" href="/auth/login">Back to login</a>
+    <Link to="/auth/login" className="link block">Back to login</Link>
 
 export default BackToLogin;
diff --git a/frontend/src/metabase/auth/containers/LoginApp.jsx b/frontend/src/metabase/auth/containers/LoginApp.jsx
index abc33827c51..e2970bebcf9 100644
--- a/frontend/src/metabase/auth/containers/LoginApp.jsx
+++ b/frontend/src/metabase/auth/containers/LoginApp.jsx
@@ -1,5 +1,6 @@
 import React, { Component, PropTypes } from "react";
 import { findDOMNode } from "react-dom";
+import { Link } from "react-router";
 import { connect } from "react-redux";
 
 import cx from "classnames";
@@ -19,8 +20,7 @@ import * as authActions from "../auth";
 const mapStateToProps = (state, props) => {
     return {
         loginError:       state.auth && state.auth.loginError,
-        user:             state.currentUser,
-        onChangeLocation: props.onChangeLocation
+        user:             state.currentUser
     }
 }
 
@@ -57,7 +57,7 @@ export default class LoginApp extends Component {
 
         this.validateForm();
 
-        const { loginGoogle, onChangeLocation } = this.props;
+        const { loginGoogle } = this.props;
 
         let ssoLoginButton = findDOMNode(this.refs.ssoLoginButton);
 
@@ -74,7 +74,7 @@ export default class LoginApp extends Component {
                       cookiepolicy: 'single_host_origin',
                   });
                   auth2.attachClickHandler(ssoLoginButton, {},
-                      (googleUser) => loginGoogle(googleUser, onChangeLocation),
+                      (googleUser) => loginGoogle(googleUser),
                       (error) => console.error('There was an error logging in', error)
                   );
                 })
@@ -89,14 +89,6 @@ export default class LoginApp extends Component {
         this.validateForm();
     }
 
-    componentWillReceiveProps(newProps) {
-        const { user, onChangeLocation } = newProps;
-            // if we already have a user then we shouldn't be logging in
-        if (user) {
-            onChangeLocation("/");
-        }
-    }
-
     onChange(fieldName, fieldValue) {
         this.setState({ credentials: { ...this.state.credentials, [fieldName]: fieldValue }});
     }
@@ -104,10 +96,10 @@ export default class LoginApp extends Component {
     formSubmitted(e) {
         e.preventDefault();
 
-        let { login, onChangeLocation } = this.props;
+        let { login } = this.props;
         let { credentials } = this.state;
 
-        login(credentials, onChangeLocation);
+        login(credentials);
     }
 
     render() {
@@ -158,7 +150,7 @@ export default class LoginApp extends Component {
                                 <button className={cx("Button Grid-cell", {'Button--primary': this.state.valid})} disabled={!this.state.valid}>
                                     Sign in
                                 </button>
-                                <a className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" href="/auth/forgot_password" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</a>
+                                <Link to="/auth/forgot_password" className="Grid-cell py2 sm-py0 text-grey-3 md-text-right text-centered flex-full link" onClick={(e) => { window.OSX ? window.OSX.resetPassword() : null }}>I seem to have forgotten my password</Link>
                             </div>
                         </form>
                     </div>
diff --git a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
index bc573a7668f..4a11c424e0d 100644
--- a/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
+++ b/frontend/src/metabase/auth/containers/PasswordResetApp.jsx
@@ -1,5 +1,6 @@
 import React, { Component, PropTypes } from "react";
 import { connect } from "react-redux";
+import { Link } from "react-router";
 import { AngularResourceProxy } from "metabase/lib/redux";
 
 import cx from "classnames";
@@ -19,11 +20,10 @@ const SessionApi = new AngularResourceProxy("Session", ["password_reset_token_va
 
 const mapStateToProps = (state, props) => {
     return {
-        token:            state.router && state.router.params && state.router.params.token,
+        token:            props.params.token,
         resetError:       state.auth && state.auth.resetError,
         resetSuccess:     state.auth && state.auth.resetSuccess,
-        newUserJoining:   state.router && state.router.location && state.router.location.hash === "#new",
-        onChangeLocation: props.onChangeLocation
+        newUserJoining:   props.location.hash === "#new"
     }
 }
 
@@ -83,10 +83,10 @@ export default class PasswordResetApp extends Component {
     formSubmitted(e) {
         e.preventDefault();
 
-        let { token, passwordReset, onChangeLocation } = this.props;
+        let { token, passwordReset } = this.props;
         let { credentials } = this.state;
 
-        passwordReset(token, credentials, onChangeLocation);
+        passwordReset(token, credentials);
     }
 
     render() {
@@ -106,7 +106,7 @@ export default class PasswordResetApp extends Component {
                                     <h3 className="Login-header Form-offset mt4">Whoops, that's an expired link</h3>
                                     <p className="Form-offset mb4 mr4">
                                         For security reasons, password reset links expire after a little while. If you still need
-                                        to reset your password, you can <a href="/auth/forgot_password" className="link">request a new reset email</a>.
+                                        to reset your password, you can <Link to="/auth/forgot_password" className="link">request a new reset email</Link>.
                                     </p>
                                 </div>
                             </div>
@@ -160,9 +160,9 @@ export default class PasswordResetApp extends Component {
                                   <p>Your password has been reset.</p>
                                   <p>
                                       { newUserJoining ?
-                                      <a href="/?new" className="Button Button--primary">Sign in with your new password</a>
+                                      <Link to="/?new" className="Button Button--primary">Sign in with your new password</Link>
                                       :
-                                      <a href="/" className="Button Button--primary">Sign in with your new password</a>
+                                      <Link to="/" className="Button Button--primary">Sign in with your new password</Link>
                                       }
                                   </p>
                               </div>
diff --git a/frontend/src/metabase/components/Breadcrumbs.jsx b/frontend/src/metabase/components/Breadcrumbs.jsx
index af080db1ba4..c32baf6d26a 100644
--- a/frontend/src/metabase/components/Breadcrumbs.jsx
+++ b/frontend/src/metabase/components/Breadcrumbs.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import S from "./Breadcrumbs.css";
 
@@ -51,7 +52,7 @@ export default class Breadcrumbs extends Component {
                                 )}
                             >
                                 { breadcrumb.length > 1 ?
-                                    <a href={breadcrumb[1]}>{breadcrumb[0]}</a> :
+                                    <Link to={breadcrumb[1]}>{breadcrumb[0]}</Link> :
                                     <span>{breadcrumb[0]}</span>
                                 }
                             </Ellipsified>
diff --git a/frontend/src/metabase/components/EmptyState.jsx b/frontend/src/metabase/components/EmptyState.jsx
index 0e66f2d34a1..2a4e5d56c68 100644
--- a/frontend/src/metabase/components/EmptyState.jsx
+++ b/frontend/src/metabase/components/EmptyState.jsx
@@ -1,4 +1,5 @@
 import React, { PropTypes } from "react";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 
@@ -17,7 +18,7 @@ const EmptyState = ({ title, message, icon, image, action, link }) =>
             <h3 className="text-grey-2 mt4" style={{maxWidth: "350px"}}>{message}</h3>
         </div>
         { action &&
-            <a className="Button Button--primary mt3" href={link} target={link.startsWith('http') ? "_blank" : ""}>{action}</a>
+            <Link to={link} className="Button Button--primary mt3" target={link.startsWith('http') ? "_blank" : ""}>{action}</Link>
         }
     </div>
 
diff --git a/frontend/src/metabase/components/NotFound.jsx b/frontend/src/metabase/components/NotFound.jsx
index 08338513869..0eb16a19945 100644
--- a/frontend/src/metabase/components/NotFound.jsx
+++ b/frontend/src/metabase/components/NotFound.jsx
@@ -1,5 +1,5 @@
 import React, { Component, PropTypes } from "react";
-
+import { Link } from "react-router";
 
 export default class NotFound extends Component {
     render() {
@@ -11,9 +11,9 @@ export default class NotFound extends Component {
                     <p className="h4">You might've been tricked by a ninja, but in all likelihood, you were just given a bad link.</p>
                     <p className="h4 my4">You can always:</p>
                     <div className="flex align-center">
-                        <a className="Button Button--primary" href="/q">
+                        <Link to="/q" className="Button Button--primary">
                             <div className="p1">Ask a new question.</div>
-                        </a>
+                        </Link>
                         <span className="mx2">or</span>
                         <a className="Button Button--withIcon" target="_blank" href="http://tv.giphy.com/kitten">
                             <div className="p1 flex align-center relative">
@@ -26,4 +26,4 @@ export default class NotFound extends Component {
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/frontend/src/metabase/controllers.js b/frontend/src/metabase/controllers.js
index 8da3c4ed76b..8b137891791 100644
--- a/frontend/src/metabase/controllers.js
+++ b/frontend/src/metabase/controllers.js
@@ -1,34 +1 @@
 
-angular
-.module('metabase.controllers', ['metabase.services'])
-.controller('Metabase', ['$scope', '$location', 'AppState', function($scope, $location, AppState) {
-
-    var clearState = function() {
-        $scope.siteName = undefined;
-        $scope.user = undefined;
-        $scope.userIsSuperuser = false;
-    };
-
-    // current User
-    $scope.user = undefined;
-    $scope.userIsSuperuser = false;
-
-    $scope.$on("appstate:site-settings", function(event, settings) {
-        // change in global settings
-        $scope.siteName = settings.site_name;
-    });
-
-    $scope.$on("appstate:user", function(event, user) {
-        // change in current user
-        $scope.user = user;
-        $scope.userIsSuperuser = user.is_superuser;
-    });
-
-    $scope.$on("appstate:logout", function(event, user) {
-        clearState();
-    });
-
-    $scope.refreshCurrentUser = function() {
-        AppState.refreshCurrentUser();
-    };
-}]);
diff --git a/frontend/src/metabase/dashboard/components/Dashboard.jsx b/frontend/src/metabase/dashboard/components/Dashboard.jsx
index c26deda7e32..6736de15b3a 100644
--- a/frontend/src/metabase/dashboard/components/Dashboard.jsx
+++ b/frontend/src/metabase/dashboard/components/Dashboard.jsx
@@ -60,7 +60,6 @@ export default class Dashboard extends Component {
         setDashCardVisualizationSetting: PropTypes.func.isRequired,
 
         onChangeLocation: PropTypes.func.isRequired,
-        onDashboardDeleted: PropTypes.func.isRequired,
     };
 
     async componentDidMount() {
@@ -101,10 +100,10 @@ export default class Dashboard extends Component {
 
     async loadDashboard(dashboardId) {
         this.loadParams();
-        const { addCardOnLoad, fetchDashboard, fetchCards, addCardToDashboard, onChangeLocation } = this.props;
+        const { addCardOnLoad, fetchDashboard, fetchCards, addCardToDashboard, onChangeLocation, location } = this.props;
 
         try {
-            await fetchDashboard(dashboardId);
+            await fetchDashboard(dashboardId, location.query);
             if (addCardOnLoad != null) {
                 // we have to load our cards before we can add one
                 await fetchCards();
@@ -285,7 +284,7 @@ export default class Dashboard extends Component {
         if (refreshElapsed >= this.state.refreshPeriod) {
             refreshElapsed = 0;
 
-            await this.props.fetchDashboard(this.props.selectedDashboard);
+            await this.props.fetchDashboard(this.props.selectedDashboard, this.props.location.query);
             this.fetchDashboardCardData(this.props);
         }
         this.setState({ refreshElapsed });
diff --git a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
index 958c1186874..d6d32275a34 100644
--- a/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
+++ b/frontend/src/metabase/dashboard/components/DashboardHeader.jsx
@@ -61,7 +61,7 @@ export default class DashboardHeader extends Component {
     }
 
     onRevert() {
-        this.props.fetchDashboard(this.props.dashboard.id);
+        this.props.fetchDashboard(this.props.dashboard.id, this.props.location.query);
     }
 
     async onSave() {
@@ -92,7 +92,7 @@ export default class DashboardHeader extends Component {
     // 3. finished reverting to a revision
     onRevertedRevision() {
         this.refs.dashboardHistory.toggle();
-        this.props.fetchDashboard(this.props.dashboard.id);
+        this.props.fetchDashboard(this.props.dashboard.id, this.props.location.query);
     }
 
     getEditingButtons() {
diff --git a/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx b/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx
index ba513b33524..c7524d1ad3b 100644
--- a/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx
+++ b/frontend/src/metabase/dashboard/containers/DashCardCardParameterMapper.jsx
@@ -24,11 +24,11 @@ import type { DatabaseId } from "metabase/meta/types/base";
 const makeMapStateToProps = () => {
     const getParameterMappingOptions = makeGetParameterMappingOptions()
     const mapStateToProps = (state, props) => ({
-        parameter:           getEditingParameter(state),
+        parameter:           getEditingParameter(state, props),
         mappingOptions:      getParameterMappingOptions(state, props),
         mappingOptionSections: _.groupBy(getParameterMappingOptions(state, props), "sectionName"),
         target:              getParameterTarget(state, props),
-        mappingsByParameter: getMappingsByParameter(state)
+        mappingsByParameter: getMappingsByParameter(state, props)
     });
     return mapStateToProps;
 }
diff --git a/frontend/src/metabase/dashboard/containers/DashboardApp.jsx b/frontend/src/metabase/dashboard/containers/DashboardApp.jsx
index 6586225730e..7dbe55ad17f 100644
--- a/frontend/src/metabase/dashboard/containers/DashboardApp.jsx
+++ b/frontend/src/metabase/dashboard/containers/DashboardApp.jsx
@@ -12,19 +12,19 @@ import * as dashboardActions from "../dashboard";
 
 const mapStateToProps = (state, props) => {
   return {
-      isEditing:            getIsEditing(state),
-      isEditingParameter:   getIsEditingParameter(state),
-      isDirty:              getIsDirty(state),
-      selectedDashboard:    getSelectedDashboard(state),
-      dashboard:            getDashboardComplete(state),
-      cards:                getCardList(state),
-      revisions:            getRevisions(state),
-      dashcardData:             getCardData(state),
-      cardDurations:        getCardDurations(state),
-      databases:            getDatabases(state),
-      editingParameter:     getEditingParameter(state),
-      parameterValues:      getParameterValues(state),
-      addCardOnLoad:        parseInt(state.router.location.query.add) || null
+      isEditing:            getIsEditing(state, props),
+      isEditingParameter:   getIsEditingParameter(state, props),
+      isDirty:              getIsDirty(state, props),
+      selectedDashboard:    getSelectedDashboard(state, props),
+      dashboard:            getDashboardComplete(state, props),
+      cards:                getCardList(state, props),
+      revisions:            getRevisions(state, props),
+      dashcardData:         getCardData(state, props),
+      cardDurations:        getCardDurations(state, props),
+      databases:            getDatabases(state, props),
+      editingParameter:     getEditingParameter(state, props),
+      parameterValues:      getParameterValues(state, props),
+      addCardOnLoad:        props.location.query.add ? parseInt(props.location.query.add) : null
   }
 }
 
diff --git a/frontend/src/metabase/dashboard/containers/ParameterWidget.jsx b/frontend/src/metabase/dashboard/containers/ParameterWidget.jsx
index 4a7c5cb02bc..cfbc1e66d2b 100644
--- a/frontend/src/metabase/dashboard/containers/ParameterWidget.jsx
+++ b/frontend/src/metabase/dashboard/containers/ParameterWidget.jsx
@@ -12,7 +12,7 @@ import { getMappingsByParameter } from "../selectors";
 
 const makeMapStateToProps = () => {
     const mapStateToProps = (state, props) => ({
-        mappingsByParameter: getMappingsByParameter(state)
+        mappingsByParameter: getMappingsByParameter(state, props)
     });
     return mapStateToProps;
 }
diff --git a/frontend/src/metabase/dashboard/dashboard.js b/frontend/src/metabase/dashboard/dashboard.js
index 84671ea3e3d..a313a7e1388 100644
--- a/frontend/src/metabase/dashboard/dashboard.js
+++ b/frontend/src/metabase/dashboard/dashboard.js
@@ -138,7 +138,7 @@ export const fetchCardData = createThunkAction(FETCH_CARD_DATA, function(card, d
         let result = null;
 
         // if we have a parameter, apply it to the card query before we execute
-        let { dashboardId } = getState().router.params;
+        let { dashboardId } = getState().dashboard;
         let { dashboards, parameterValues } = getState().dashboard;
 
         let dashboard = dashboards[dashboardId];
@@ -188,19 +188,26 @@ export const fetchCardDuration = createThunkAction(FETCH_CARD_DURATION, function
     };
 });
 
-export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(id, enableQueryParameters = true, enableDefaultParameters = true) {
+const SET_DASHBOARD_ID = "metabase/dashboard/SET_DASHBOARD_ID";
+export const setDashboardId = createAction(SET_DASHBOARD_ID);
+
+export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(dashId, queryParams, enableDefaultParameters = true) {
     return async function(dispatch, getState) {
-        let result = await DashboardApi.get({ dashId: id });
+        let result = await DashboardApi.get({ dashId: dashId });
+
+        dispatch(setDashboardId(dashId));
+
         if (result.parameters) {
-            const { query } = getState().router.location;
             for (const parameter of result.parameters) {
-                if (enableQueryParameters && query[parameter.slug] != null) {
-                    dispatch(setParameterValue(parameter.id, query[parameter.slug]));
+                if (queryParams && queryParams[parameter.slug] != null) {
+                    dispatch(setParameterValue(parameter.id, queryParams[parameter.slug]));
                 } else if (enableDefaultParameters && parameter.default != null) {
                     dispatch(setParameterValue(parameter.id, parameter.default));
                 }
             }
         }
+
+        // fetch database metadata for every card
         _.chain(result.ordered_cards)
             .map((dc) => [dc.card].concat(dc.series))
             .flatten()
@@ -214,10 +221,10 @@ export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(id, en
 
 export const saveDashboard = createThunkAction(SAVE_DASHBOARD, function(dashId) {
     return async function(dispatch, getState) {
-        let { dashboards, dashcards } = getState().dashboard;
+        let { dashboards, dashcards, dashboardId } = getState().dashboard;
         let dashboard = {
-            ...dashboards[dashId],
-            ordered_cards: dashboards[dashId].ordered_cards.map(dashcardId => dashcards[dashcardId])
+            ...dashboards[dashboardId],
+            ordered_cards: dashboards[dashboardId].ordered_cards.map(dashcardId => dashcards[dashcardId])
         };
 
         // remove isRemoved dashboards
@@ -270,7 +277,7 @@ export const saveDashboard = createThunkAction(SAVE_DASHBOARD, function(dashId)
         }
 
         // make sure that we've fully cleared out any dirty state from editing (this is overkill, but simple)
-        dispatch(fetchDashboard(dashId, false, true)); // disable using query parameters when saving
+        dispatch(fetchDashboard(dashId, null, true)); // disable using query parameters when saving
 
         MetabaseAnalytics.trackEvent("Dashboard", "Update");
 
@@ -328,6 +335,10 @@ export const setParameterValue = createThunkAction(SET_PARAMETER_VALUE, (paramet
 
 // reducers
 
+const dashboardId = handleActions({
+    [SET_DASHBOARD_ID]: { next: (state, { payload }) => payload }
+}, null);
+
 const isEditing = handleActions({
     [SET_EDITING_DASHBOARD]: { next: (state, { payload }) => payload }
 }, false);
@@ -422,6 +433,7 @@ const dashboardListing = handleActions({
 }, []);
 
 export default combineReducers({
+    dashboardId,
     isEditing,
     cards,
     cardList,
diff --git a/frontend/src/metabase/dashboard/selectors.js b/frontend/src/metabase/dashboard/selectors.js
index 68392ddf9da..73da45afaf6 100644
--- a/frontend/src/metabase/dashboard/selectors.js
+++ b/frontend/src/metabase/dashboard/selectors.js
@@ -13,7 +13,7 @@ import Query from "metabase/lib/query";
 import type { CardObject } from "metabase/meta/types/Card";
 import type { ParameterMappingOption, ParameterObject } from "metabase/meta/types/Dashboard";
 
-export const getSelectedDashboard = state => state.router.params.dashboardId;
+export const getSelectedDashboard = (state, props) => props.params.dashboardId;
 export const getIsEditing         = state => state.dashboard.isEditing;
 export const getCards             = state => state.dashboard.cards;
 export const getDashboards        = state => state.dashboard.dashboards;
diff --git a/frontend/src/metabase/home/components/Activity.jsx b/frontend/src/metabase/home/components/Activity.jsx
index a8067cb5c7c..d2eecea0d25 100644
--- a/frontend/src/metabase/home/components/Activity.jsx
+++ b/frontend/src/metabase/home/components/Activity.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from 'react';
+import { Link } from "react-router";
 import _ from 'underscore';
 
 import LoadingAndErrorWrapper from 'metabase/components/LoadingAndErrorWrapper.jsx';
@@ -88,7 +89,7 @@ export default class Activity extends Component {
             case "card-create":
             case "card-update":
                 if(item.table) {
-                    description.summary = (<span>saved a question about <a data-metabase-event={"Activity Feed;Header Clicked;Database -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id)}>{item.table.display_name}</a></span>);
+                    description.summary = (<span>saved a question about <Link to={Urls.tableRowsQuery(item.database_id, item.table_id)} data-metabase-event={"Activity Feed;Header Clicked;Database -> "+item.topic} className="link text-dark">{item.table.display_name}</Link></span>);
                 } else {
                     description.summary = "saved a question";
                 }
@@ -104,14 +105,14 @@ export default class Activity extends Component {
                 break;
             case "dashboard-add-cards":
                 if(item.model_exists) {
-                    description.summary = (<span>added a question to the dashboard - <a data-metabase-event={"Activity Feed;Header Clicked;Dashboard -> "+item.topic} className="link text-dark" href={Urls.dashboard(item.model_id)}>{item.details.name}</a></span>);
+                    description.summary = (<span>added a question to the dashboard - <Link to={Urls.dashboard(item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Dashboard -> "+item.topic} className="link text-dark">{item.details.name}</Link></span>);
                 } else {
                     description.summary = (<span>added a question to the dashboard - <span className="text-dark">{item.details.name}</span></span>);
                 }
                 break;
             case "dashboard-remove-cards":
                 if(item.model_exists) {
-                    description.summary = (<span>removed a question from the dashboard - <a data-metabase-event={"Activity Feed;Header Clicked;Dashboard -> "+item.topic} className="link text-dark" href={Urls.dashboard(item.model_id)}>{item.details.name}</a></span>);
+                    description.summary = (<span>removed a question from the dashboard - <Link to={Urls.dashboard(item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Dashboard -> "+item.topic} className="link text-dark">{item.details.name}</Link></span>);
                 } else {
                     description.summary = (<span>removed a question from the dashboard - <span className="text-dark">{item.details.name}</span></span>);
                 }
@@ -133,14 +134,14 @@ export default class Activity extends Component {
                 break;
             case "metric-create":
                 if(item.model_exists) {
-                    description.summary = (<span>added the metric <a data-metabase-event={"Activity Feed;Header Clicked;Metric -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id, item.model_id)}>{item.details.name}</a> to the <a data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id)}>{item.table.display_name}</a> table</span>);
+                    description.summary = (<span>added the metric <Link to={Urls.tableRowsQuery(item.database_id, item.table_id, item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Metric -> "+item.topic} className="link text-dark">{item.details.name}</Link> to the <Link to={Urls.tableRowsQuery(item.database_id, item.table_id)} data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark">{item.table.display_name}</Link> table</span>);
                 } else {
                     description.summary = (<span>added the metric <span className="text-dark">{item.details.name}</span></span>);
                 }
                 break;
             case "metric-update":
                 if(item.model_exists) {
-                    description.summary = (<span>made changes to the metric <a data-metabase-event={"Activity Feed;Header Clicked;Metric -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id, item.model_id)}>{item.details.name}</a> in the <a data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id)}>{item.table.display_name}</a> table</span>);
+                    description.summary = (<span>made changes to the metric <Link to={Urls.tableRowsQuery(item.database_id, item.table_id, item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Metric -> "+item.topic} className="link text-dark">{item.details.name}</Link> in the <Link to={Urls.tableRowsQuery(item.database_id, item.table_id)} data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark">{item.table.display_name}</Link> table</span>);
                 } else {
                     description.summary = (<span>made changes to the metric <span className="text-dark">{item.details.name}</span></span>);
                 }
@@ -156,14 +157,22 @@ export default class Activity extends Component {
                 break;
             case "segment-create":
                 if(item.model_exists) {
-                    description.summary = (<span>added the filter <a data-metabase-event={"Activity Feed;Header Clicked;Segment -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id, null, item.model_id)}>{item.details.name}</a> to the <a data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id)}>{item.table.display_name}</a> table</span>);
+                    description.summary = (
+                        <span>
+                            added the filter
+                            <Link to={Urls.tableRowsQuery(item.database_id, item.table_id, null, item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Segment -> "+item.topic} className="link text-dark">{item.details.name}</Link>
+                            to the
+                            <Link to={Urls.tableRowsQuery(item.database_id, item.table_id)} data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark">{item.table.display_name}</Link>
+                            table
+                        </span>
+                    );
                 } else {
                     description.summary = (<span>added the filter <span className="text-dark">{item.details.name}</span></span>);
                 }
                 break;
             case "segment-update":
                 if(item.model_exists) {
-                    description.summary = (<span>made changes to the filter <a data-metabase-event={"Activity Feed;Header Clicked;Segment -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id, null, item.model_id)}>{item.details.name}</a> in the <a data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark" href={Urls.tableRowsQuery(item.database_id, item.table_id)}>{item.table.display_name}</a> table</span>);
+                    description.summary = (<span>made changes to the filter <Link to={Urls.tableRowsQuery(item.database_id, item.table_id, null, item.model_id)} data-metabase-event={"Activity Feed;Header Clicked;Segment -> "+item.topic} className="link text-dark">{item.details.name}</Link> in the <Link to={Urls.tableRowsQuery(item.database_id, item.table_id)} data-metabase-event={"Activity Feed;Header Clicked;Table -> "+item.topic} className="link text-dark">{item.table.display_name}</Link> table</span>);
                 } else {
                     description.summary = (<span>made changes to the filter <span className="text-dark">{item.details.name}</span></span>);
                 }
diff --git a/frontend/src/metabase/home/components/ActivityStory.jsx b/frontend/src/metabase/home/components/ActivityStory.jsx
index 8fd50b1c0d4..93446a019a5 100644
--- a/frontend/src/metabase/home/components/ActivityStory.jsx
+++ b/frontend/src/metabase/home/components/ActivityStory.jsx
@@ -1,5 +1,5 @@
 import React, { Component, PropTypes } from 'react';
-
+import { Link } from "react-router";
 
 export default class ActivityStory extends Component {
     constructor(props, context) {
@@ -26,7 +26,7 @@ export default class ActivityStory extends Component {
             <div className="mt1 border-left flex mr2" style={{borderWidth: '3px', marginLeft: '22px', borderColor: '#F2F5F6'}}>
                 <div className="flex full ml4 bordered rounded p2" style={this.styles}>
                     { story.bodyLink ?
-                        <a data-metabase-event={"Activity Feed;Story Clicked;"+story.topic} className="link" href={story.bodyLink}>{story.body}</a>
+                        <Link to={story.bodyLink} data-metabase-event={"Activity Feed;Story Clicked;"+story.topic} className="link">{story.body}</Link>
                     :
                         <span>{story.body}</span>
                     }
diff --git a/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx b/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
index e47ecb2cdef..5722a7e1b4a 100644
--- a/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
+++ b/frontend/src/metabase/home/components/NewUserOnboardingModal.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import MetabaseSettings from "metabase/lib/settings";
 
@@ -83,7 +84,7 @@ export default class NewUserOnboardingModal extends Component {
                             {this.renderStep()}
                             <span className="flex-align-right">
                                 <a className="text-underline-hover cursor-pointer mr3" onClick={() => (this.closeModal())}>skip for now</a>
-                                <a className="Button Button--primary" href="/q?tutorial">Let's do it!</a>
+                                <Link to="/q?tutorial" className="Button Button--primary">Let's do it!</Link>
                             </span>
                         </div>
                     </div>
diff --git a/frontend/src/metabase/home/components/NextStep.jsx b/frontend/src/metabase/home/components/NextStep.jsx
index 806223f9dcb..a3d1ef4a52f 100644
--- a/frontend/src/metabase/home/components/NextStep.jsx
+++ b/frontend/src/metabase/home/components/NextStep.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 import fetch from 'isomorphic-fetch';
 
 import SidebarSection from "./SidebarSection.jsx";
@@ -30,11 +31,11 @@ export default class NextStep extends Component {
         const { next } = this.state;
         if (next) {
             return (
-                <SidebarSection title="Setup Tip" icon="info" extra={<a className="text-brand no-decoration" href="/admin/settings">View all</a>}>
-                    <a className="block p3 no-decoration" href={next.link}>
+                <SidebarSection title="Setup Tip" icon="info" extra={<Link to="/admin/settings" className="text-brand no-decoration">View all</Link>}>
+                    <Link to={next.link} className="block p3 no-decoration">
                         <h4 className="text-brand text-bold">{next.title}</h4>
                         <p className="m0 mt1">{next.description}</p>
-                    </a>
+                    </Link>
                 </SidebarSection>
             )
         } else {
diff --git a/frontend/src/metabase/home/components/RecentViews.jsx b/frontend/src/metabase/home/components/RecentViews.jsx
index 4fef7d5b860..f21955be677 100644
--- a/frontend/src/metabase/home/components/RecentViews.jsx
+++ b/frontend/src/metabase/home/components/RecentViews.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 import SidebarSection from "./SidebarSection.jsx";
@@ -54,7 +55,7 @@ export default class RecentViews extends Component {
                         {recentViews.map((item, index) =>
                             <li key={index} className="py1 ml1 flex align-center clearfix">
                                 {this.renderIllustration(item)}
-                                <a data-metabase-event={"Recent Views;"+item.model+";"+item.cnt} className="ml1 flex-full link" href={Urls.modelToUrl(item.model, item.model_id)}>{item.model_object.name}</a>
+                                <Link to={Urls.modelToUrl(item.model, item.model_id)} data-metabase-event={"Recent Views;"+item.model+";"+item.cnt} className="ml1 flex-full link">{item.model_object.name}</Link>
                             </li>
                         )}
                     </ul>
diff --git a/frontend/src/metabase/home/containers/HomepageApp.jsx b/frontend/src/metabase/home/containers/HomepageApp.jsx
index 53ebb22d079..b5514b92512 100644
--- a/frontend/src/metabase/home/containers/HomepageApp.jsx
+++ b/frontend/src/metabase/home/containers/HomepageApp.jsx
@@ -12,19 +12,22 @@ import NewUserOnboardingModal from '../components/NewUserOnboardingModal.jsx';
 import NextStep from "../components/NextStep.jsx";
 
 import * as homepageActions from "../actions";
-import { getActivity, getRecentViews, getUser, getShowOnboarding } from "../selectors";
+import { getActivity, getRecentViews, getUser } from "../selectors";
 
 const mapStateToProps = (state, props) => {
     return {
         activity:       getActivity(state),
         recentViews:    getRecentViews(state),
         user:           getUser(state),
-        showOnboarding: getShowOnboarding(state)
+        showOnboarding: "new" in props.location.query
     }
 }
 
+import { push } from "react-router-redux";
+
 const mapDispatchToProps = {
-    ...homepageActions
+    ...homepageActions,
+    onChangeLocation: push
 }
 
 @connect(mapStateToProps, mapDispatchToProps)
diff --git a/frontend/src/metabase/home/selectors.js b/frontend/src/metabase/home/selectors.js
index 75a8b5430ef..45f2d4420af 100644
--- a/frontend/src/metabase/home/selectors.js
+++ b/frontend/src/metabase/home/selectors.js
@@ -1,4 +1,3 @@
 export const getActivity 		= (state) => state.home && state.home.activity
 export const getRecentViews 	= (state) => state.home && state.home.recentViews
 export const getUser 			= (state) => state.currentUser
-export const getShowOnboarding 	= (state) => state.router && state.router.location && "new" in state.router.location.query
diff --git a/frontend/src/metabase/lib/card.js b/frontend/src/metabase/lib/card.js
index 23bf207f838..fa82c25c3ad 100644
--- a/frontend/src/metabase/lib/card.js
+++ b/frontend/src/metabase/lib/card.js
@@ -82,6 +82,8 @@ export function serializeCardForUrl(card) {
 }
 
 export function deserializeCardFromUrl(serialized) {
+    serialized = serialized.replace(/^#/, "");
+    console.log("deserializing", serialized)
     return JSON.parse(b64url_to_utf8(serialized));
 }
 
diff --git a/frontend/src/metabase/lib/cookies.js b/frontend/src/metabase/lib/cookies.js
index fdfd9eca242..4d832e850c8 100644
--- a/frontend/src/metabase/lib/cookies.js
+++ b/frontend/src/metabase/lib/cookies.js
@@ -1,45 +1,40 @@
 
 import { clearGoogleAuthCredentials } from "metabase/lib/auth";
 
-export const METABASE_SESSION_COOKIE = 'metabase.SESSION_ID';
-
-var mb_cookies = {};
+// import Cookies from "js-cookie";
 
+export const METABASE_SESSION_COOKIE = 'metabase.SESSION_ID';
 
 // Handles management of Metabase cookie work
 var MetabaseCookies = {
-    // a little weird, but needed to keep us hooked in with Angular
-    bootstrap: function($rootScope, $location, ipCookie) {
-        mb_cookies.scope = $rootScope;
-        mb_cookies.location = $location;
-        mb_cookies.ipCookie = ipCookie;
-    },
-
     // set the session cookie.  if sessionId is null, clears the cookie
     setSessionCookie: function(sessionId) {
-        if (sessionId) {
-            // set a session cookie
-            var isSecure = (mb_cookies.location.protocol() === "https") ? true : false;
-            mb_cookies.ipCookie(METABASE_SESSION_COOKIE, sessionId, {
-                path: '/',
-                expires: 14,
-                secure: isSecure
-            });
-
-            // send a login notification event
-            mb_cookies.scope.$broadcast('appstate:login', sessionId);
-
-        } else {
-            sessionId = mb_cookies.ipCookie(METABASE_SESSION_COOKIE);
-
-            // delete the current session cookie and Google Auth creds
-            mb_cookies.ipCookie.remove(METABASE_SESSION_COOKIE);
-            clearGoogleAuthCredentials();
-
-            // send a logout notification event
-            mb_cookies.scope.$broadcast('appstate:logout', sessionId);
-
-            return sessionId;
+        let ipCookie = angular.element(document.querySelector("body")).injector().get("ipCookie");
+
+        const options = {
+            path: '/',
+            expires: 14,
+            secure: window.location.protocol === "https:"
+        };
+
+        try {
+            if (sessionId) {
+                // set a session cookie
+                // Cookies.set(METABASE_SESSION_COOKIE, sessionId);
+                ipCookie(METABASE_SESSION_COOKIE, sessionId, options);
+            } else {
+                // sessionId = Cookies.get(METABASE_SESSION_COOKIE);
+                sessionId = ipCookie(METABASE_SESSION_COOKIE);
+
+                // delete the current session cookie and Google Auth creds
+                // Cookies.remove(METABASE_SESSION_COOKIE);
+                ipCookie.remove(METABASE_SESSION_COOKIE);
+                clearGoogleAuthCredentials();
+
+                return sessionId;
+            }
+        } catch (e) {
+            console.error("setSessionCookie:", e);
         }
     }
 }
diff --git a/frontend/src/metabase/nav/components/ProfileLink.jsx b/frontend/src/metabase/nav/components/ProfileLink.jsx
index d180adca1e5..0dc608f2764 100644
--- a/frontend/src/metabase/nav/components/ProfileLink.jsx
+++ b/frontend/src/metabase/nav/components/ProfileLink.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from 'react';
+import { Link } from "react-router";
 import OnClickOut from 'react-onclickout';
 import cx from 'classnames';
 import _ from "underscore";
@@ -74,24 +75,24 @@ export default class ProfileLink extends Component {
                         <div className="NavDropdown-content right">
                             <ul className="NavDropdown-content-layer">
                                 <li>
-                                    <a data-metabase-event={"Navbar;Profile Dropdown;Edit Profile"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration" href="/user/edit_current">
+                                    <Link to="/user/edit_current" data-metabase-event={"Navbar;Profile Dropdown;Edit Profile"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration">
                                         Account Settings
-                                    </a>
+                                    </Link>
                                 </li>
 
                                 { user.is_superuser && context !== 'admin' ?
                                     <li>
-                                        <a data-metabase-event={"Navbar;Profile Dropdown;Enter Admin"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration" href="/admin/">
+                                        <Link to="/admin" data-metabase-event={"Navbar;Profile Dropdown;Enter Admin"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration">
                                             Admin Panel
-                                        </a>
+                                        </Link>
                                     </li>
                                 : null }
 
                                 { user.is_superuser && context === 'admin' ?
                                     <li>
-                                        <a data-metabase-event={"Navbar;Profile Dropdown;Exit Admin"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration" href="/">
+                                        <Link to="/" data-metabase-event={"Navbar;Profile Dropdown;Exit Admin"} onClick={this.closeDropdown} className="Dropdown-item block text-white no-decoration">
                                             Exit Admin
-                                        </a>
+                                        </Link>
                                     </li>
                                 : null }
 
@@ -116,7 +117,13 @@ export default class ProfileLink extends Component {
                                 </li>
 
                                 <li className="border-top border-light">
-                                    <a data-metabase-event={"Navbar;Profile Dropdown;Logout"} className="Dropdown-item block text-white no-decoration" href="/auth/logout">Logout</a>
+                                    <Link
+                                        to="/auth/logout"
+                                        data-metabase-event={"Navbar;Profile Dropdown;Logout"}
+                                        className="Dropdown-item block text-white no-decoration"
+                                    >
+                                        Logout
+                                    </Link>
                                 </li>
                             </ul>
                         </div>
diff --git a/frontend/src/metabase/nav/containers/DashboardsDropdown.jsx b/frontend/src/metabase/nav/containers/DashboardsDropdown.jsx
index c0e499263f0..6ef3182e0db 100644
--- a/frontend/src/metabase/nav/containers/DashboardsDropdown.jsx
+++ b/frontend/src/metabase/nav/containers/DashboardsDropdown.jsx
@@ -1,5 +1,6 @@
 import React, { Component, PropTypes } from 'react';
 import { connect } from "react-redux";
+import { Link } from "react-router";
 
 import OnClickOut from 'react-onclickout';
 
@@ -135,7 +136,7 @@ export default class DashboardsDropdown extends Component {
                                     <ul className="NavDropdown-content-layer">
                                         { dashboards.map(dash =>
                                             <li key={dash.id} className="block">
-                                                <a data-metabase-event={"Navbar;Dashboard Dropdown;Open Dashboard;"+dash.id} className="Dropdown-item block text-white no-decoration" href={"/dash/"+dash.id} onClick={this.closeDropdown}>
+                                                <Link to={"/dash/"+dash.id} data-metabase-event={"Navbar;Dashboard Dropdown;Open Dashboard;"+dash.id} className="Dropdown-item block text-white no-decoration" onClick={this.closeDropdown}>
                                                     <div className="flex text-bold">
                                                         {dash.name}
                                                     </div>
@@ -144,7 +145,7 @@ export default class DashboardsDropdown extends Component {
                                                             {dash.description}
                                                         </div>
                                                     : null }
-                                                </a>
+                                                </Link>
                                             </li>
                                         )}
                                         <li className="block border-top border-light">
diff --git a/frontend/src/metabase/nav/containers/Navbar.jsx b/frontend/src/metabase/nav/containers/Navbar.jsx
index 848444e8d78..45f04d3cca0 100644
--- a/frontend/src/metabase/nav/containers/Navbar.jsx
+++ b/frontend/src/metabase/nav/containers/Navbar.jsx
@@ -2,6 +2,8 @@ import React, { Component, PropTypes } from 'react';
 import cx from "classnames";
 
 import { connect } from "react-redux";
+import { push } from "react-router-redux";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 import LogoIcon from "metabase/components/LogoIcon.jsx";
@@ -12,12 +14,13 @@ import ProfileLink from "metabase/nav/components/ProfileLink.jsx";
 import { getPath, getContext, getUser } from "../selectors";
 
 const mapStateToProps = (state, props) => ({
-    path:       getPath(state),
-    context:    getContext(state),
+    path:       getPath(state, props),
+    context:    getContext(state, props),
     user:       getUser(state)
 });
 
 const mapDispatchToProps = {
+    onChangeLocation: push
 };
 
 @connect(mapStateToProps, mapDispatchToProps)
@@ -54,7 +57,9 @@ export default class Navbar extends Component {
     }
 
     renderAdminNav() {
-        const classes = "NavItem py1 px2 no-decoration";
+        const getClasses = (path) => cx("NavItem py1 px2 no-decoration", {
+            "is--selected": this.isActive(path)
+        });
 
         return (
             <nav className={cx("Nav AdminNav", this.props.className)}>
@@ -66,24 +71,24 @@ export default class Navbar extends Component {
 
                     <ul className="sm-ml4 flex flex-full">
                         <li>
-                            <a data-metabase-event={"Navbar;Settings"} className={cx(classes, {"is--selected": this.isActive("/admin/settings")})}  href="/admin/settings/">
+                            <Link to="/admin/settings" data-metabase-event={"Navbar;Settings"} className={getClasses("/admin/settings")}  >
                                 Settings
-                            </a>
+                            </Link>
                         </li>
                         <li>
-                            <a data-metabase-event={"Navbar;People"} className={cx(classes, {"is--selected": this.isActive("/admin/people")})} href="/admin/people/">
+                            <Link to="/admin/people" data-metabase-event={"Navbar;People"} className={getClasses("/admin/people")} >
                                 People
-                            </a>
+                            </Link>
                         </li>
                         <li>
-                            <a data-metabase-event={"Navbar;Data Model"} className={cx(classes, {"is--selected": this.isActive("/admin/datamodel")})} href="/admin/datamodel/database">
+                            <Link to="/admin/datamodel/database" data-metabase-event={"Navbar;Data Model"} className={getClasses("/admin/datamodel")} >
                                 Data Model
-                            </a>
+                            </Link>
                         </li>
                         <li>
-                            <a data-metabase-event={"Navbar;Databases"} className={cx(classes, {"is--selected": this.isActive("/admin/databases")})} href="/admin/databases/">
+                            <Link to="/admin/databases" data-metabase-event={"Navbar;Databases"} className={getClasses("/admin/databases")}>
                                 Databases
-                            </a>
+                            </Link>
                         </li>
                     </ul>
 
@@ -98,9 +103,9 @@ export default class Navbar extends Component {
             <nav className={cx("Nav py2 sm-py1 xl-py3 relative", this.props.className)}>
                 <ul className="wrapper flex align-center">
                     <li>
-                        <a data-metabase-event={"Navbar;Logo"} className="NavItem cursor-pointer flex align-center" href="/">
+                        <Link to="/" data-metabase-event={"Navbar;Logo"} className="NavItem cursor-pointer flex align-center">
                             <LogoIcon className="text-brand my2"></LogoIcon>
-                        </a>
+                        </Link>
                     </li>
                 </ul>
             </nav>
@@ -112,9 +117,9 @@ export default class Navbar extends Component {
             <nav className={cx("Nav CheckBg CheckBg-offset relative bg-brand sm-py2 sm-py1 xl-py3", this.props.className)}>
                 <ul className="pl4 pr1 flex align-center">
                     <li>
-                        <a data-metabase-event={"Navbar;Logo"} className="NavItem cursor-pointer text-white flex align-center my1 transition-background" href="/">
-                            <span><LogoIcon className="text-white m1"></LogoIcon></span>
-                        </a>
+                        <Link to="/" data-metabase-event={"Navbar;Logo"} className="NavItem cursor-pointer text-white flex align-center my1 transition-background">
+                            <LogoIcon className="text-white m1"></LogoIcon>
+                        </Link>
                     </li>
                     <li className="pl3">
                         <DashboardsDropdown {...this.props}>
@@ -127,16 +132,16 @@ export default class Navbar extends Component {
                         </DashboardsDropdown>
                     </li>
                     <li className="pl1">
-                        <a data-metabase-event={"Navbar;Questions"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background", {"NavItem--selected": this.isActive("/questions") })} href="/questions/all">Questions</a>
+                        <Link to="/questions/all" data-metabase-event={"Navbar;Questions"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background")} activeClassName="NavItem--selected">Questions</Link>
                     </li>
                     <li className="pl1">
-                        <a data-metabase-event={"Navbar;Pulses"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background", {"NavItem--selected": this.isActive("/pulse") })} href="/pulse/">Pulses</a>
+                        <Link to="/pulse" data-metabase-event={"Navbar;Pulses"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background")} activeClassName="NavItem--selected">Pulses</Link>
                     </li>
                     <li className="pl1">
-                        <a data-metabase-event={"Navbar;DataReference"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background", {"NavItem--selected": this.isActive("/reference") })} href="/reference/guide">Data Reference</a>
+                        <Link to="/reference/guide" data-metabase-event={"Navbar;DataReference"} style={this.styles.navButton} className={cx("NavItem cursor-pointer text-white text-bold no-decoration flex align-center px2 transition-background")} activeClassName="NavItem--selected">Data Reference</Link>
                     </li>
                     <li className="pl3">
-                        <a data-metabase-event={"Navbar;New Question"} style={this.styles.newQuestion} className="NavNewQuestion rounded inline-block bg-white text-brand text-bold cursor-pointer px2 no-decoration transition-all" href="/q">New <span className="hide sm-show">Question</span></a>
+                        <Link to="/q" data-metabase-event={"Navbar;New Question"} style={this.styles.newQuestion} className="NavNewQuestion rounded inline-block bg-white text-brand text-bold cursor-pointer px2 no-decoration transition-all">New <span className="hide sm-show">Question</span></Link>
                     </li>
                     <li className="flex-align-right transition-background">
                         <div className="inline-block text-white"><ProfileLink {...this.props}></ProfileLink></div>
diff --git a/frontend/src/metabase/nav/selectors.js b/frontend/src/metabase/nav/selectors.js
index 05d832607fb..612ad051c26 100644
--- a/frontend/src/metabase/nav/selectors.js
+++ b/frontend/src/metabase/nav/selectors.js
@@ -1,7 +1,7 @@
 
 import { createSelector } from 'reselect';
 
-export const getPath = (state) => state.router.location.pathname;
+export const getPath = (state, props) => props.location.pathname;
 
 export const getUser = (state) => state.currentUser;
 
diff --git a/frontend/src/metabase/pulse/components/PulseEdit.jsx b/frontend/src/metabase/pulse/components/PulseEdit.jsx
index deed84d1d12..9a5d7e5e0e3 100644
--- a/frontend/src/metabase/pulse/components/PulseEdit.jsx
+++ b/frontend/src/metabase/pulse/components/PulseEdit.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import PulseEditName from "./PulseEditName.jsx";
 import PulseEditCards from "./PulseEditCards.jsx";
@@ -148,7 +149,7 @@ export default class PulseEdit extends Component {
                         failedText="Save failed"
                         successText="Saved"
                     />
-                    <a className="text-bold flex-align-right no-decoration text-brand-hover" href="/pulse">Cancel</a>
+                    <Link to="/pulse" className="text-bold flex-align-right no-decoration text-brand-hover">Cancel</Link>
                 </div>
             </div>
         );
diff --git a/frontend/src/metabase/pulse/components/PulseListItem.jsx b/frontend/src/metabase/pulse/components/PulseListItem.jsx
index 1515eb8cc8d..5d01ad3289f 100644
--- a/frontend/src/metabase/pulse/components/PulseListItem.jsx
+++ b/frontend/src/metabase/pulse/components/PulseListItem.jsx
@@ -1,5 +1,6 @@
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
+import { Link } from "react-router";
 
 import cx from "classnames";
 
@@ -31,15 +32,15 @@ export default class PulseListItem extends Component {
                         <span>Pulse by <span className="text-bold">{pulse.creator && pulse.creator.common_name}</span></span>
                     </div>
                     <div className="flex-align-right">
-                        <a className="PulseEditButton PulseButton Button no-decoration text-bold" href={"/pulse/" + pulse.id}>Edit</a>
+                        <Link to={"/pulse/" + pulse.id} className="PulseEditButton PulseButton Button no-decoration text-bold">Edit</Link>
                     </div>
                 </div>
                 <ol className="mb2 px4 flex flex-wrap">
                     { pulse.cards.map((card, index) =>
                         <li key={index} className="mr1 mb1">
-                            <a className="Button" href={Urls.card(card.id)}>
+                            <Link to={Urls.card(card.id)} className="Button">
                                 {card.name}
-                            </a>
+                            </Link>
                         </li>
                     )}
                 </ol>
diff --git a/frontend/src/metabase/pulse/components/SetupMessage.jsx b/frontend/src/metabase/pulse/components/SetupMessage.jsx
index 36fde9b7cc8..d953094b269 100644
--- a/frontend/src/metabase/pulse/components/SetupMessage.jsx
+++ b/frontend/src/metabase/pulse/components/SetupMessage.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 
@@ -30,7 +31,7 @@ export default class SetupMessage extends Component {
                     </div>
                     <div className="mt2">
                         {channels.map(c =>
-                            <a className="Button Button--primary mr1" href={"/admin/settings?section="+c} target={window.OSX ? null : "_blank"}>Configure {c}</a>
+                            <Link to={"/admin/settings/"+c.toLowerCase()} className="Button Button--primary mr1" target={window.OSX ? null : "_blank"}>Configure {c}</Link>
                         )}
                     </div>
                 </div>
diff --git a/frontend/src/metabase/pulse/containers/PulseEditApp.jsx b/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
index 86f56f94d6e..be8ef590beb 100644
--- a/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
+++ b/frontend/src/metabase/pulse/containers/PulseEditApp.jsx
@@ -6,7 +6,7 @@ import { editPulseSelectors } from "../selectors";
 
 const mapStateToProps = (state, props) => {
     return {
-        ...editPulseSelectors(state),
+        ...editPulseSelectors(state, props),
         user: state.currentUser
         // onChangeLocation: onChangeLocation
     }
diff --git a/frontend/src/metabase/pulse/containers/PulseListApp.jsx b/frontend/src/metabase/pulse/containers/PulseListApp.jsx
index 593702023f8..6567524c0a1 100644
--- a/frontend/src/metabase/pulse/containers/PulseListApp.jsx
+++ b/frontend/src/metabase/pulse/containers/PulseListApp.jsx
@@ -7,7 +7,7 @@ import { listPulseSelectors } from "../selectors";
 
 const mapStateToProps = (state, props) => {
     return {
-        ...listPulseSelectors(state),
+        ...listPulseSelectors(state, props),
         user: state.currentUser,
         // onChangeLocation: onChangeLocation
     }
diff --git a/frontend/src/metabase/pulse/selectors.js b/frontend/src/metabase/pulse/selectors.js
index a1a0bfa44ab..7386a312f2a 100644
--- a/frontend/src/metabase/pulse/selectors.js
+++ b/frontend/src/metabase/pulse/selectors.js
@@ -30,19 +30,7 @@ const userListSelector = createSelector(
     (users) => Object.values(users)
 );
 
-const getPulseId = createSelector(
-    state => state.router,
-    (router) => {
-        if (router && router.params && router.params.pulseId) {
-            return parseInt(router.params.pulseId);
-        } else if (router && router.location && router.location.hash) {
-            return parseInt(router.location.hash.substr(1));
-        } else {
-            return null;
-        }
-    }
-);
-
+const getPulseId = (state, props) => props.params.pulseId ? parseInt(props.params.pulseId) : null;
 
 // LIST
 export const listPulseSelectors = createSelector(
diff --git a/frontend/src/metabase/query_builder/QueryVisualization.jsx b/frontend/src/metabase/query_builder/QueryVisualization.jsx
index c1b9226320f..ad137db4543 100644
--- a/frontend/src/metabase/query_builder/QueryVisualization.jsx
+++ b/frontend/src/metabase/query_builder/QueryVisualization.jsx
@@ -1,5 +1,6 @@
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
+import { Link } from "react-router";
 
 import Icon from "metabase/components/Icon.jsx";
 import LoadingSpinner from 'metabase/components/LoadingSpinner.jsx';
@@ -259,5 +260,5 @@ export default class QueryVisualization extends Component {
 const VisualizationEmptyState = ({showTutorialLink}) =>
     <div className="flex full layout-centered text-grey-1 flex-column">
         <h1>If you give me some data I can show you something cool. Run a Query!</h1>
-        { showTutorialLink && <a className="link cursor-pointer my2" href="/q?tutorial">How do I use this thing?</a> }
+        { showTutorialLink && <Link to="/q?tutorial" className="link cursor-pointer my2">How do I use this thing?</Link> }
     </div>
diff --git a/frontend/src/metabase/query_builder/actions.js b/frontend/src/metabase/query_builder/actions.js
index 5a1e5c8fa25..e2dabba0267 100644
--- a/frontend/src/metabase/query_builder/actions.js
+++ b/frontend/src/metabase/query_builder/actions.js
@@ -6,9 +6,10 @@ import i from "icepick";
 import moment from "moment";
 
 import { AngularResourceProxy, angularPromise, createThunkAction } from "metabase/lib/redux";
+import { push, replace } from "react-router-redux";
 
 import MetabaseAnalytics from "metabase/lib/analytics";
-import { loadCard, isCardDirty, startNewCard, deserializeCardFromUrl } from "metabase/lib/card";
+import { loadCard, isCardDirty, startNewCard, deserializeCardFromUrl, serializeCardForUrl, cleanCopyCard, urlForCardState } from "metabase/lib/card";
 import { formatSQL, humanize } from "metabase/lib/formatting";
 import Query from "metabase/lib/query";
 import { createQuery } from "metabase/lib/query";
@@ -20,11 +21,50 @@ import { getParameters } from "./selectors";
 const Metabase = new AngularResourceProxy("Metabase", ["db_list_with_tables", "db_fields", "dataset", "table_query_metadata"]);
 const User = new AngularResourceProxy("User", ["update_qbnewb"]);
 
+import { parse as urlParse } from "url";
+
+function updateUrl(card, isDirty=false, replaceState=false) {
+    if (!card) {
+        return;
+    }
+    var copy = cleanCopyCard(card);
+    var newState = {
+        card: copy,
+        cardId: copy.id,
+        serializedCard: serializeCardForUrl(copy)
+    };
+
+    // FIXME: history.state is not what we expect when using react-router
+    if (angular.equals(window.history.state, newState)) {
+        return;
+    }
+
+    var url = urlForCardState(newState, isDirty);
+
+    // if the serialized card is identical replace the previous state instead of adding a new one
+    // e.x. when saving a new card we want to replace the state and URL with one with the new card ID
+    // FIXME: history.state is not what we expect when using react-router
+    replaceState = replaceState || (window.history.state && window.history.state.serializedCard === newState.serializedCard);
+
+    const urlParsed = urlParse(url);
+    const locationDescriptor = {
+        pathname: urlParsed.pathname,
+        search: urlParsed.search,
+        hash: urlParsed.hash,
+        state: newState
+    };
+
+    if (replaceState) {
+        return replace(locationDescriptor);
+    } else {
+        return push(locationDescriptor);
+    }
+}
 
 export const INITIALIZE_QB = "INITIALIZE_QB";
-export const initializeQB = createThunkAction(INITIALIZE_QB, (updateUrl) => {
+export const initializeQB = createThunkAction(INITIALIZE_QB, (location, params) => {
     return async (dispatch, getState) => {
-        const { router: { location, params: { cardId } }, currentUser } = getState();
+        const { currentUser } = getState();
 
         let card, databases, originalCard, uiControls = {};
 
@@ -45,11 +85,11 @@ export const initializeQB = createThunkAction(INITIALIZE_QB, (updateUrl) => {
         // load up or initialize the card we'll be working on
         const serializedCard = location.hash || null;
         const sampleDataset = _.findWhere(databases, { is_sample: true });
-        if (cardId || serializedCard) {
+        if (params.cardId || serializedCard) {
             // existing card being loaded
             try {
-                if (cardId) {
-                    card = await loadCard(cardId);
+                if (params.cardId) {
+                    card = await loadCard(params.cardId);
 
                     // when we are loading from a card id we want an explict clone of the card we loaded which is unmodified
                     originalCard = JSON.parse(JSON.stringify(card));
@@ -64,10 +104,10 @@ export const initializeQB = createThunkAction(INITIALIZE_QB, (updateUrl) => {
                 MetabaseAnalytics.trackEvent("QueryBuilder", "Query Loaded", card.dataset_query.type);
 
                 // if we have deserialized card from the url AND loaded a card by id then the user should be dropped into edit mode
-                uiControls.isEditing = (location.query.edit || (cardId && serializedCard)) ? true : false;
+                uiControls.isEditing = (location.query.edit || (params.cardId && serializedCard)) ? true : false;
 
                 // if this is the users first time loading a saved card on the QB then show them the newb modal
-                if (cardId && currentUser.is_qbnewb) {
+                if (params.cardId && currentUser.is_qbnewb) {
                     uiControls.isShowingNewbModal = true;
                     MetabaseAnalytics.trackEvent("QueryBuilder", "Show Newb Modal");
                 }
@@ -120,14 +160,13 @@ export const initializeQB = createThunkAction(INITIALIZE_QB, (updateUrl) => {
         }
 
         // clean up the url and make sure it reflects our card state
-        updateUrl(card, isCardDirty(card, originalCard));
+        dispatch(updateUrl(card, isCardDirty(card, originalCard)));
 
         return {
             card,
             originalCard,
             databases,
-            uiControls,
-            updateUrl
+            uiControls
         };
     };
 });
@@ -167,7 +206,7 @@ export const beginEditing = createAction(BEGIN_EDITING, () => {
 export const CANCEL_EDITING = "CANCEL_EDITING";
 export const cancelEditing = createThunkAction(CANCEL_EDITING, () => {
     return (dispatch, getState) => {
-        const { qb: { originalCard, updateUrl } } = getState();
+        const { qb: { originalCard } } = getState();
 
         // clone
         let card = JSON.parse(JSON.stringify(originalCard));
@@ -176,7 +215,7 @@ export const cancelEditing = createThunkAction(CANCEL_EDITING, () => {
 
         // we do this to force the indication of the fact that the card should not be considered dirty when the url is updated
         dispatch(runQuery(card, false));
-        updateUrl(card, false);
+        dispatch(updateUrl(card, false));
 
         MetabaseAnalytics.trackEvent("QueryBuilder", "Edit Cancel");
         return card;
@@ -264,9 +303,9 @@ export const setCardAttribute = createAction(SET_CARD_ATTRIBUTE, (attr, value) =
 export const SET_CARD_VISUALIZATION = "SET_CARD_VISUALIZATION";
 export const setCardVisualization = createThunkAction(SET_CARD_VISUALIZATION, (display) => {
     return (dispatch, getState) => {
-        const { qb: { card, uiControls, updateUrl } } = getState();
+        const { qb: { card, uiControls } } = getState();
         let updatedCard = updateVisualizationSettings(card, uiControls.isEditing, display, card.visualization_settings);
-        updateUrl(updatedCard, true);
+        dispatch(updateUrl(updatedCard, true));
         return updatedCard;
     }
 });
@@ -274,9 +313,9 @@ export const setCardVisualization = createThunkAction(SET_CARD_VISUALIZATION, (d
 export const SET_CARD_VISUALIZATION_SETTING = "SET_CARD_VISUALIZATION_SETTING";
 export const setCardVisualizationSetting = createThunkAction(SET_CARD_VISUALIZATION_SETTING, (path, value) => {
     return (dispatch, getState) => {
-        const { qb: { card, uiControls, updateUrl } } = getState();
+        const { qb: { card, uiControls } } = getState();
         let updatedCard = updateVisualizationSettings(card, uiControls.isEditing, card.display, i.assocIn(card.visualization_settings, path, value));
-        updateUrl(updatedCard, true);
+        dispatch(updateUrl(updatedCard, true));
         return updatedCard;
     };
 });
@@ -284,9 +323,9 @@ export const setCardVisualizationSetting = createThunkAction(SET_CARD_VISUALIZAT
 export const SET_CARD_VISUALIZATION_SETTINGS = "SET_CARD_VISUALIZATION_SETTINGS";
 export const setCardVisualizationSettings = createThunkAction(SET_CARD_VISUALIZATION_SETTINGS, (settings) => {
     return (dispatch, getState) => {
-        const { qb: { card, uiControls, updateUrl } } = getState();
+        const { qb: { card, uiControls } } = getState();
         let updatedCard = updateVisualizationSettings(card, uiControls.isEditing, card.display, settings);
-        updateUrl(updatedCard, true);
+        dispatch(updateUrl(updatedCard, true));
         return updatedCard;
     };
 });
@@ -326,13 +365,11 @@ export const setParameterValue = createThunkAction(SET_PARAMETER_VALUE, (paramet
 export const NOTIFY_CARD_CREATED = "NOTIFY_CARD_CREATED";
 export const notifyCardCreatedFn = createThunkAction(NOTIFY_CARD_CREATED, (card) => {
     return (dispatch, getState) => {
-        const { qb: { updateUrl } } = getState();
-
         dispatch(loadMetadataForCard(card));
 
         // we do this to force the indication of the fact that the card should not be considered dirty when the url is updated
         dispatch(runQuery(card, false));
-        updateUrl(card, false);
+        dispatch(updateUrl(card, false));
 
         MetabaseAnalytics.trackEvent("QueryBuilder", "Create Card", card.dataset_query.type);
 
@@ -343,13 +380,11 @@ export const notifyCardCreatedFn = createThunkAction(NOTIFY_CARD_CREATED, (card)
 export const NOTIFY_CARD_UPDATED = "NOTIFY_CARD_UPDATED";
 export const notifyCardUpdatedFn = createThunkAction("NOTIFY_CARD_UPDATED", (card) => {
     return (dispatch, getState) => {
-        const { qb: { updateUrl } } = getState();
-
         dispatch(loadMetadataForCard(card));
 
         // we do this to force the indication of the fact that the card should not be considered dirty when the url is updated
         dispatch(runQuery(card, false));
-        updateUrl(card, false);
+        dispatch(updateUrl(card, false));
 
         MetabaseAnalytics.trackEvent("QueryBuilder", "Update Card", card.dataset_query.type);
 
@@ -361,7 +396,7 @@ export const notifyCardUpdatedFn = createThunkAction("NOTIFY_CARD_UPDATED", (car
 export const RELOAD_CARD = "RELOAD_CARD";
 export const reloadCard = createThunkAction(RELOAD_CARD, () => {
     return async (dispatch, getState) => {
-        const { qb: { originalCard, updateUrl } } = getState();
+        const { qb: { originalCard } } = getState();
 
         // clone
         let card = JSON.parse(JSON.stringify(originalCard));
@@ -370,7 +405,7 @@ export const reloadCard = createThunkAction(RELOAD_CARD, () => {
 
         // we do this to force the indication of the fact that the card should not be considered dirty when the url is updated
         dispatch(runQuery(card, false));
-        updateUrl(card, false);
+        dispatch(updateUrl(card, false));
 
         return card;
     };
@@ -687,7 +722,7 @@ export const setQuerySort = createThunkAction(SET_QUERY_SORT, (column) => {
 
 // runQuery
 export const RUN_QUERY = "RUN_QUERY";
-export const runQuery = createThunkAction(RUN_QUERY, (card, updateUrl=true, paramValues) => {
+export const runQuery = createThunkAction(RUN_QUERY, (card, shouldUpdateUrl=true, paramValues) => {
     return async (dispatch, getState) => {
         const state = getState();
         const parameters = getParameters(state);
@@ -720,8 +755,8 @@ export const runQuery = createThunkAction(RUN_QUERY, (card, updateUrl=true, para
             }).filter(p => p);
         }
 
-        if (updateUrl) {
-            state.qb.updateUrl(card, cardIsDirty);
+        if (shouldUpdateUrl) {
+            dispatch(updateUrl(card, cardIsDirty));
         }
 
         let cancelQueryDeferred = angularPromise();
diff --git a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
index 1064dbdc7b6..435e5b72791 100644
--- a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
+++ b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
@@ -70,10 +70,9 @@ function autocompleteResults(card, prefix) {
 
 const mapStateToProps = (state, props) => {
     return {
-        updateUrl:                 props.updateUrl,
         user:                      state.currentUser,
-        fromUrl:                   state.router && state.router.location && state.router.location.query.from,
-        location:                  state.router && state.router.location,
+        fromUrl:                   props.location.query.from,
+        location:                  props.location,
 
         card:                      card(state),
         originalCard:              originalCard(state),
@@ -124,7 +123,7 @@ export default class QueryBuilder extends Component {
     }
 
     componentWillMount() {
-        this.props.initializeQB(this.props.updateUrl);
+        this.props.initializeQB(this.props.location, this.props.params);
     }
 
     componentDidMount() {
@@ -141,7 +140,7 @@ export default class QueryBuilder extends Component {
         }
         // HACK: if we switch to the tutorial from within the QB we need to manually re-initialize
         if (!this.props.location.query.tutorial && nextProps.location.query.tutorial) {
-            this.props.initializeQB(nextProps.updateUrl);
+            this.props.initializeQB(this.props.location, this.props.params);
         }
     }
 
@@ -157,6 +156,7 @@ export default class QueryBuilder extends Component {
     }
 
     popStateListener(e) {
+        // FIXME: e.state is not what we expect when using react-router
         if (e.state && e.state.card) {
             e.preventDefault();
             this.props.setCardAndRun(e.state.card);
diff --git a/frontend/src/metabase/query_builder/reducers.js b/frontend/src/metabase/query_builder/reducers.js
index 2092bbfc532..877fdde76cb 100644
--- a/frontend/src/metabase/query_builder/reducers.js
+++ b/frontend/src/metabase/query_builder/reducers.js
@@ -36,11 +36,6 @@ import {
 } from "./actions";
 
 
-// TODO: these are here as work arounds until we are transitioned over to ReduxRouter and using their history approach
-export const updateUrl = handleActions({
-    [INITIALIZE_QB]: { next: (state, { payload }) => payload ? payload.updateUrl : state }
-}, () => console.log("default"));
-
 // TODO: once we are using the global redux store we can get this from there
 export const user = handleActions({
     [CLOSE_QB_NEWB_MODAL]: { next: (state, { payload }) => ({...state, is_qbnewb: false}) }
diff --git a/frontend/src/metabase/reducers.js b/frontend/src/metabase/reducers.js
new file mode 100644
index 00000000000..5759d1bec38
--- /dev/null
+++ b/frontend/src/metabase/reducers.js
@@ -0,0 +1,65 @@
+
+import { combineReducers } from 'redux';
+
+import auth from "metabase/auth/auth";
+
+/* ducks */
+import metadata from "metabase/redux/metadata";
+import requests from "metabase/redux/requests";
+
+/* admin */
+import settings from "metabase/admin/settings/settings";
+import * as people from "metabase/admin/people/reducers";
+import databases from "metabase/admin/databases/database";
+import datamodel from "metabase/admin/datamodel/metadata";
+
+/* dashboards */
+import dashboard from "metabase/dashboard/dashboard";
+import * as home from "metabase/home/reducers";
+
+/* questions / query builder */
+import questions from "metabase/questions/questions";
+import labels from "metabase/questions/labels";
+import undo from "metabase/questions/undo";
+import * as qb from "metabase/query_builder/reducers";
+
+/* data reference */
+import reference from "metabase/reference/reference";
+
+/* pulses */
+import * as pulse from "metabase/pulse/reducers";
+
+/* setup */
+import * as setup from "metabase/setup/reducers";
+
+/* user */
+import * as user from "metabase/user/reducers";
+import { currentUser } from "metabase/user";
+
+const reducers = {
+    // global reducers
+    auth,
+    currentUser,
+    metadata,
+    requests,
+
+    // main app reducers
+    dashboard,
+    home: combineReducers(home),
+    labels,
+    pulse: combineReducers(pulse),
+    qb: combineReducers(qb),
+    questions,
+    reference,
+    setup: combineReducers(setup),
+    undo,
+    user: combineReducers(user),
+
+    // admin reducers
+    databases,
+    datamodel: datamodel,
+    people: combineReducers(people),
+    settings
+};
+
+export default reducers;
diff --git a/frontend/src/metabase/reference/containers/ReferenceApp.jsx b/frontend/src/metabase/reference/containers/ReferenceApp.jsx
index dd47ced0486..e2c86b284ae 100644
--- a/frontend/src/metabase/reference/containers/ReferenceApp.jsx
+++ b/frontend/src/metabase/reference/containers/ReferenceApp.jsx
@@ -27,12 +27,12 @@ import {
 } from 'metabase/questions/questions';
 
 const mapStateToProps = (state, props) => ({
-    sectionId: getSectionId(state),
-    databaseId: getDatabaseId(state),
-    sections: getSections(state),
-    section: getSection(state),
-    breadcrumbs: getBreadcrumbs(state),
-    isEditing: getIsEditing(state)
+    sectionId: getSectionId(state, props),
+    databaseId: getDatabaseId(state, props),
+    sections: getSections(state, props),
+    section: getSection(state, props),
+    breadcrumbs: getBreadcrumbs(state, props),
+    isEditing: getIsEditing(state, props)
 });
 
 const mapDispatchToProps = {
diff --git a/frontend/src/metabase/reference/containers/ReferenceEntity.jsx b/frontend/src/metabase/reference/containers/ReferenceEntity.jsx
index 867e5b981b1..3140debceb8 100644
--- a/frontend/src/metabase/reference/containers/ReferenceEntity.jsx
+++ b/frontend/src/metabase/reference/containers/ReferenceEntity.jsx
@@ -46,17 +46,17 @@ import * as metadataActions from 'metabase/redux/metadata';
 import * as actions from 'metabase/reference/reference';
 
 const mapStateToProps = (state, props) => ({
-    section: getSection(state),
-    entity: getData(state) || {},
-    loading: getLoading(state),
+    section: getSection(state, props),
+    entity: getData(state, props) || {},
+    loading: getLoading(state, props),
     // naming this 'error' will conflict with redux form
-    loadingError: getError(state),
-    user: getUser(state),
-    foreignKeys: getForeignKeys(state),
-    isEditing: getIsEditing(state),
-    hasSingleSchema: getHasSingleSchema(state),
-    hasDisplayName: getHasDisplayName(state),
-    hasRevisionHistory: getHasRevisionHistory(state)
+    loadingError: getError(state, props),
+    user: getUser(state, props),
+    foreignKeys: getForeignKeys(state, props),
+    isEditing: getIsEditing(state, props),
+    hasSingleSchema: getHasSingleSchema(state, props),
+    hasDisplayName: getHasDisplayName(state, props),
+    hasRevisionHistory: getHasRevisionHistory(state, props)
 });
 
 const mapDispatchToProps = {
diff --git a/frontend/src/metabase/reference/containers/ReferenceEntityList.jsx b/frontend/src/metabase/reference/containers/ReferenceEntityList.jsx
index 0eae0f94624..0d45f4e7255 100644
--- a/frontend/src/metabase/reference/containers/ReferenceEntityList.jsx
+++ b/frontend/src/metabase/reference/containers/ReferenceEntityList.jsx
@@ -33,12 +33,12 @@ import {
 import * as metadataActions from "metabase/redux/metadata";
 
 const mapStateToProps = (state, props) => ({
-    section: getSection(state),
-    entities: getData(state),
-    user: getUser(state),
-    hasSingleSchema: getHasSingleSchema(state),
-    loading: getLoading(state),
-    loadingError: getError(state)
+    section: getSection(state, props),
+    entities: getData(state, props),
+    user: getUser(state, props),
+    hasSingleSchema: getHasSingleSchema(state, props),
+    loading: getLoading(state, props),
+    loadingError: getError(state, props)
 });
 
 const mapDispatchToProps = {
diff --git a/frontend/src/metabase/reference/containers/ReferenceFieldsList.jsx b/frontend/src/metabase/reference/containers/ReferenceFieldsList.jsx
index 79af13f641b..13d5442917a 100644
--- a/frontend/src/metabase/reference/containers/ReferenceFieldsList.jsx
+++ b/frontend/src/metabase/reference/containers/ReferenceFieldsList.jsx
@@ -37,15 +37,15 @@ import * as metadataActions from "metabase/redux/metadata";
 import * as actions from 'metabase/reference/reference';
 
 const mapStateToProps = (state, props) => {
-    const data = getData(state);
+    const data = getData(state, props);
     return {
-        section: getSection(state),
+        section: getSection(state, props),
         entities: data,
-        foreignKeys: getForeignKeys(state),
-        loading: getLoading(state),
-        loadingError: getError(state),
-        user: getUser(state),
-        isEditing: getIsEditing(state),
+        foreignKeys: getForeignKeys(state, props),
+        loading: getLoading(state, props),
+        loadingError: getError(state, props),
+        user: getUser(state, props),
+        isEditing: getIsEditing(state, props),
         fields: fieldsToFormFields(data)
     };
 }
diff --git a/frontend/src/metabase/reference/containers/ReferenceRevisionsList.jsx b/frontend/src/metabase/reference/containers/ReferenceRevisionsList.jsx
index c37ebd395d3..0f592bb09b5 100644
--- a/frontend/src/metabase/reference/containers/ReferenceRevisionsList.jsx
+++ b/frontend/src/metabase/reference/containers/ReferenceRevisionsList.jsx
@@ -27,14 +27,14 @@ import ReferenceHeader from "../components/ReferenceHeader.jsx";
 
 const mapStateToProps = (state, props) => {
     return {
-        section: getSection(state),
-        revisions: getData(state),
-        metric: getMetric(state),
-        segment: getSegment(state),
-        tables: getTables(state),
-        user: getUser(state),
-        loading: getLoading(state),
-        loadingError: getError(state)
+        section: getSection(state, props),
+        revisions: getData(state, props),
+        metric: getMetric(state, props),
+        segment: getSegment(state, props),
+        tables: getTables(state, props),
+        user: getUser(state, props),
+        loading: getLoading(state, props),
+        loadingError: getError(state, props)
     }
 }
 
diff --git a/frontend/src/metabase/reference/selectors.js b/frontend/src/metabase/reference/selectors.js
index 946ce2c76aa..472c09f75c5 100644
--- a/frontend/src/metabase/reference/selectors.js
+++ b/frontend/src/metabase/reference/selectors.js
@@ -67,7 +67,7 @@ const referenceSections = {
     }
 };
 
-const getReferenceSections = (state) => referenceSections;
+const getReferenceSections = (state, props) => referenceSections;
 
 const getMetricSections = (metric, table, user) => metric ? {
     [`/reference/metrics/${metric.id}`]: {
@@ -293,34 +293,34 @@ const idsToObjectMap = (ids, objects) => ids
     // hangs browser for large databases
     // .reduce((map, object) => i.assoc(map, object.id, object), {});
 
-export const getUser = (state) => state.currentUser;
+export const getUser = (state, props) => state.currentUser;
 
-export const getSectionId = (state) => state.router.location.pathname;
+export const getSectionId = (state, props) => props.location.pathname;
 
-export const getMetricId = (state) => Number.parseInt(state.router.params.metricId);
-const getMetrics = (state) => state.metadata.metrics;
+export const getMetricId = (state, props) => Number.parseInt(props.params.metricId);
+const getMetrics = (state, props) => state.metadata.metrics;
 export const getMetric = createSelector(
     [getMetricId, getMetrics],
     (metricId, metrics) => metrics[metricId] || { id: metricId }
 );
 
-export const getSegmentId = (state) => Number.parseInt(state.router.params.segmentId);
-const getSegments = (state) => state.metadata.segments;
+export const getSegmentId = (state, props) => Number.parseInt(props.params.segmentId);
+const getSegments = (state, props) => state.metadata.segments;
 export const getSegment = createSelector(
     [getSegmentId, getSegments],
     (segmentId, segments) => segments[segmentId] || { id: segmentId }
 );
 
-export const getDatabaseId = (state) => Number.parseInt(state.router.params.databaseId);
-const getDatabases = (state) => state.metadata.databases;
+export const getDatabaseId = (state, props) => Number.parseInt(props.params.databaseId);
+const getDatabases = (state, props) => state.metadata.databases;
 const getDatabase = createSelector(
     [getDatabaseId, getDatabases],
     (databaseId, databases) => databases[databaseId] || { id: databaseId }
 );
 
-export const getTableId = (state) => Number.parseInt(state.router.params.tableId);
-// export const getTableId = (state) => Number.parseInt(state.router.params.tableId);
-export const getTables = (state) => state.metadata.tables;
+export const getTableId = (state, props) => Number.parseInt(props.params.tableId);
+// export const getTableId = (state, props) => Number.parseInt(props.params.tableId);
+export const getTables = (state, props) => state.metadata.tables;
 const getTablesByDatabase = createSelector(
     [getTables, getDatabase],
     (tables, database) => tables && database && database.tables ?
@@ -339,8 +339,8 @@ const getTableByMetric = createSelector(
     (metric, tables) => metric ? tables[metric.table_id] : {}
 );
 
-export const getFieldId = (state) => Number.parseInt(state.router.params.fieldId);
-const getFields = (state) => state.metadata.fields;
+export const getFieldId = (state, props) => Number.parseInt(props.params.fieldId);
+const getFields = (state, props) => state.metadata.fields;
 const getFieldsByTable = createSelector(
     [getTable, getFields],
     (table, fields) => table && table.fields ? idsToObjectMap(table.fields, fields) : {}
@@ -358,7 +358,7 @@ const getFieldBySegment = createSelector(
     (fieldId, fields) => fields[fieldId] || { id: fieldId }
 );
 
-const getQuestions = (state) => i.getIn(state, ['questions', 'entities', 'cards']) || {};
+const getQuestions = (state, props) => i.getIn(state, ['questions', 'entities', 'cards']) || {};
 
 const getMetricQuestions = createSelector(
     [getMetricId, getQuestions],
@@ -370,7 +370,7 @@ const getMetricQuestions = createSelector(
         .reduce((map, question) => i.assoc(map, question.id, question), {})
 );
 
-const getRevisions = (state) => state.metadata.revisions;
+const getRevisions = (state, props) => state.metadata.revisions;
 
 const getMetricRevisions = createSelector(
     [getMetricId, getRevisions],
@@ -512,8 +512,8 @@ const dataSelectors = {
     getFieldsBySegment
 };
 
-export const getData = (state) => {
-    const section = getSection(state);
+export const getData = (state, props) => {
+    const section = getSection(state, props);
     if (!section) {
         return {};
     }
@@ -522,12 +522,12 @@ export const getData = (state) => {
         return {};
     }
 
-    return selector(state);
+    return selector(state, props);
 };
 
-export const getLoading = (state) => state.reference.isLoading;
+export const getLoading = (state, props) => state.reference.isLoading;
 
-export const getError = (state) => state.reference.error;
+export const getError = (state, props) => state.reference.error;
 
 const getBreadcrumb = (section, index, sections) => index !== sections.length - 1 ?
     [section.breadcrumb, section.id] : [section.breadcrumb];
@@ -573,4 +573,4 @@ export const getHasRevisionHistory = createSelector(
         section.type === 'segment'
 )
 
-export const getIsEditing = (state) => state.reference.isEditing;
+export const getIsEditing = (state, props) => state.reference.isEditing;
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index 5bf983386c6..16ff80d6e05 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -1,213 +1,4 @@
-import _ from "underscore";
-
-import MetabaseAnalytics from 'metabase/lib/analytics';
-import MetabaseCookies from 'metabase/lib/cookies';
-import MetabaseSettings from 'metabase/lib/settings';
-
-
-var MetabaseServices = angular.module('metabase.services', ['http-auth-interceptor', 'ipCookie', 'metabase.core.services']);
-
-MetabaseServices.factory('AppState', ['$rootScope', '$q', '$location', '$interval', '$timeout', 'ipCookie', 'Session', 'User', 'Settings',
-    function($rootScope, $q, $location, $interval, $timeout, ipCookie, Session, User, Settings) {
-        // this is meant to be a global service used for keeping track of our overall app state
-        // we fire 2 events as things change in the app
-        // 1. appstate:user
-
-        var initPromise;
-        var currentUserPromise;
-
-        var service = {
-
-            model: {
-                currentUser: null
-            },
-
-            init: function() {
-
-                if (!initPromise) {
-                    // hackery to allow MetabaseCookies to tie into Angular
-                    MetabaseCookies.bootstrap($rootScope, $location, ipCookie);
-
-                    var deferred = $q.defer();
-                    initPromise = deferred.promise;
-
-                    // grab our global settings
-                    service.refreshSiteSettings();
-
-                    // just make sure we grab the current user
-                    service.refreshCurrentUser().then(function(user) {
-                        deferred.resolve();
-                    }, function(error) {
-                        deferred.resolve();
-                    });
-                }
-
-                return initPromise;
-            },
-
-            clearState: function() {
-                currentUserPromise = null;
-                service.model.currentUser = null;
-
-                // clear any existing session cookies if they exist
-                ipCookie.remove('metabase.SESSION_ID');
-            },
-
-            refreshCurrentUser: function() {
-
-                // this is meant to be called once on app startup
-                var userRefresh = User.current(function(result) {
-                    service.model.currentUser = result;
-
-                    $rootScope.$broadcast('appstate:user', result);
-
-                }, function(error) {
-                    console.log('unable to get current user', error);
-                });
-
-                // NOTE: every time we refresh the user we update our current promise to ensure that
-                //       we can guarantee we've resolved the current user
-                currentUserPromise = userRefresh.$promise;
-
-                return currentUserPromise;
-            },
-
-            refreshSiteSettings: function() {
-
-                var settingsRefresh = Session.properties(function(settings) {
-
-                    MetabaseSettings.setAll(_.omit(settings, function(value, key, object) {
-                        return (key.indexOf('$') === 0);
-                    }));
-
-                    $rootScope.$broadcast('appstate:site-settings', settings);
-
-                }, function(error) {
-                    console.log('unable to get site settings', error);
-                });
-
-                return settingsRefresh.$promise;
-            },
-
-            // This function performs whatever state cleanup and next steps are required when a user tries to access
-            // something they are not allowed to.
-            invalidAccess: function(user, url, message) {
-                $location.path('/unauthorized/');
-            },
-
-            locationChanged: function(event) {
-                // this code is here to ensure that we have resolved our currentUser BEFORE we execute any other
-                // code meant to establish app context based on the current route
-                if (currentUserPromise) {
-                    currentUserPromise.then(function(user) {
-                        service.locationChangedImpl(event);
-                    }, function(error) {
-                        service.locationChangedImpl(event);
-                    });
-                } else {
-                    service.locationChangedImpl(event);
-                }
-            },
-
-            locationChangedImpl: function(event) {
-                // whenever we have a route change (including initial page load) we need to establish some context
-
-                // handle routing protections for /setup/
-                if ($location.path().indexOf('/setup') === 0 && !MetabaseSettings.hasSetupToken()) {
-                    // someone trying to access setup process without having a setup token, so block that.
-                    $location.path('/');
-                    return;
-                } else if ($location.path().indexOf('/setup') !== 0 && MetabaseSettings.hasSetupToken()) {
-                    // someone who has a setup token but isn't routing to setup yet, so send them there!
-                    $location.path('/setup/');
-                    return;
-                }
-
-                // if we don't have a current user then the only sensible destination is the login page
-                if (!service.model.currentUser) {
-                    // make sure we clear out any current state just to be safe
-                    service.clearState();
-
-                    if ($location.path().indexOf('/auth/') !== 0 && $location.path().indexOf('/setup/') !== 0) {
-                        // if the user is asking for a url outside of /auth/* then record the url then send them
-                        // to login page, otherwise we will let the user continue on to their requested page
-                        $location.path('/auth/login');
-                    }
-
-                    return;
-                }
-
-                if ($location.path().indexOf('/admin/') === 0) {
-                    // the user is trying to change to a superuser page
-                    if (!service.model.currentUser.is_superuser) {
-                        service.invalidAccess(service.model.currentUser, $location.url(), "user is not a superuser!!!");
-                        return;
-                    }
-
-                }
-            },
-        };
-
-        // listen for location changes and use that as a trigger for page view tracking
-        $rootScope.$on('$locationChangeSuccess', function(event) {
-        service.locationChanged(event);
-            // NOTE: we are only taking the path right now to avoid accidentally grabbing sensitive data like table/field ids
-            MetabaseAnalytics.trackPageView($location.path());
-        });
-
-        // login just took place, so lets force a refresh of the current user
-        $rootScope.$on("appstate:login", function(event, session_id) {
-            service.refreshCurrentUser();
-        });
-
-        // logout just took place, do some cleanup
-        $rootScope.$on("appstate:logout", function(event, session_id) {
-
-            // clear out any current state
-            service.clearState();
-
-            // NOTE that we don't really care about callbacks in this case
-            Session.delete({
-                'session_id': session_id
-            });
-        });
-
-        // enable / disable GA based on opt-out of anonymous tracking
-        $rootScope.$on("appstate:site-settings", function(event, settings) {
-            const ga_code = MetabaseSettings.get('ga_code');
-            if (MetabaseSettings.isTrackingEnabled()) {
-                // we are doing tracking
-                window['ga-disable-'+ga_code] = null;
-            } else {
-                // tracking is disabled
-                window['ga-disable-'+ga_code] = true;
-            }
-        });
-
-        // NOTE: the below events are generated from the http-auth-interceptor which listens on our $http calls
-        //       and intercepts calls that result in a 401 or 403 so that we can handle them here.  You must be
-        //       careful to consider the implications of this because any endpoint that returns a 401/403 can
-        //       have its call stack interrupted now and handled here instead of its normal callback sequence.
-
-        // $http interceptor received a 401 response
-        $rootScope.$on("event:auth-loginRequired", function() {
-            // this is effectively just like a logout, we want to reset everything to a base state, then force login
-            service.clearState();
-
-            // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
-            $timeout(function() {
-                $location.path('/auth/login');
-            }, 300);
-        });
-
-        // $http interceptor received a 403 response
-        $rootScope.$on("event:auth-forbidden", function() {
-            $location.path("/unauthorized");
-        });
-
-        return service;
-    }
-]);
+angular.module('metabase.services', ['metabase.core.services']);
 
 // API Services
 var CoreServices = angular.module('metabase.core.services', ['ngResource', 'ngCookies']);
diff --git a/frontend/src/metabase/setup/components/Setup.jsx b/frontend/src/metabase/setup/components/Setup.jsx
index e7aab9dcdc2..40d223d79e5 100644
--- a/frontend/src/metabase/setup/components/Setup.jsx
+++ b/frontend/src/metabase/setup/components/Setup.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 
 import LogoIcon from 'metabase/components/LogoIcon.jsx';
 import NewsletterForm from 'metabase/components/NewsletterForm.jsx';
@@ -82,7 +83,7 @@ export default class Setup extends Component {
                                         <NewsletterForm initialEmail={userDetails.email} />
                                     </div>
                                     <div className="pt4 pb2">
-                                        <a className="Button Button--primary" href="/?new" onClick={this.completeSetup.bind(this)}>Take me to Metabase</a>
+                                        <Link to="/?new" className="Button Button--primary" onClick={this.completeSetup.bind(this)}>Take me to Metabase</Link>
                                     </div>
                                 </section>
                             : null }
diff --git a/frontend/src/metabase/store.js b/frontend/src/metabase/store.js
new file mode 100644
index 00000000000..23c65f9b1b3
--- /dev/null
+++ b/frontend/src/metabase/store.js
@@ -0,0 +1,59 @@
+import { combineReducers, applyMiddleware, createStore, compose } from 'redux'
+import { reducer as form } from "redux-form";
+
+import { DEBUG } from "metabase/lib/debug";
+
+import thunk from "redux-thunk";
+import promise from "redux-promise";
+import logger from "redux-logger";
+
+const devToolsExtension = window.devToolsExtension ? window.devToolsExtension() : (f => f);
+
+let middleware = [thunk, promise];
+if (DEBUG) {
+    middleware.push(logger());
+}
+
+// START react-router-redux
+import { routerReducer, routerMiddleware } from 'react-router-redux'
+// END react-router-redux
+
+// START redux-router
+// import { reduxReactRouter, routerStateReducer} from 'redux-router';
+// import { createHistory } from 'history';
+// END redux-router
+
+import appReducers from './reducers';
+
+// Combine your base app reducer with the router reducer,
+// now the application data will be in the "app" prop
+// and the routing data will be in the "routing" prop of the State.
+export function getStore(history, intialState) {
+    const reducer = combineReducers({
+        ...appReducers,
+        form,
+        // START react-router-redux
+        routing: routerReducer,
+        // END react-router-redux
+
+        // START redux-router
+        // router: routerStateReducer,
+        // end redux-router
+    })
+
+    // START react-router-redux
+    middleware.push(routerMiddleware(history));
+    // END react-router-redux
+
+    // Apply this middleware to the Store.
+    return createStore(reducer, intialState, compose(
+        applyMiddleware(...middleware),
+
+        // START redux-router
+        // applyMiddleware(middleware),
+        // reduxReactRouter({ routes, createHistory }),
+        // END redux-router
+
+        devToolsExtension
+    ));
+}
diff --git a/frontend/src/metabase/user.js b/frontend/src/metabase/user.js
index 4aa015311cb..7dd8c53fd3a 100644
--- a/frontend/src/metabase/user.js
+++ b/frontend/src/metabase/user.js
@@ -4,7 +4,19 @@ import { handleActions } from 'redux-actions';
 
 export const setUser = createAction("SET_USER");
 
+export const refreshCurrentUser = createAction("REFRESH_CURRENT_USER", async function getCurrentUser() {
+    try {
+        let response = await fetch("/api/user/current", { credentials: 'same-origin' });
+        if (response.status === 200) {
+            return await response.json();
+        }
+    } catch (e) {
+        console.warn("couldn't get user", e)
+    }
+    return null;
+})
 
 export const currentUser = handleActions({
-    ["SET_USER"]: { next: (state, { payload }) => payload }
+    ["SET_USER"]: { next: (state, { payload }) => payload },
+    ["REFRESH_CURRENT_USER"]: { next: (state, { payload }) => payload }
 }, null);
diff --git a/frontend/src/metabase/user/actions.js b/frontend/src/metabase/user/actions.js
index d4c77a43491..0f6bc7f6230 100644
--- a/frontend/src/metabase/user/actions.js
+++ b/frontend/src/metabase/user/actions.js
@@ -3,9 +3,10 @@ import { createAction } from "redux-actions";
 import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
 
 // resource wrappers
-const AppState = new AngularResourceProxy("AppState", ["refreshCurrentUser"]);
+// const AppState = new AngularResourceProxy("AppState", ["refreshCurrentUser"]);
 const UserApi = new AngularResourceProxy("User", ["update", "update_password"]);
 
+import { refreshCurrentUser } from "metabase/user";
 
 // action constants
 export const CHANGE_TAB = 'CHANGE_TAB';
@@ -44,7 +45,8 @@ export const updateUser = createThunkAction(UPDATE_USER, function(user) {
         try {
             await UserApi.update(user);
 
-            AppState.refreshCurrentUser();
+            // AppState.refreshCurrentUser();
+            dispatch(refreshCurrentUser());
 
             return {
                 success: true,
diff --git a/frontend/src/metabase/visualizations/Scalar.jsx b/frontend/src/metabase/visualizations/Scalar.jsx
index 14b89d1d6bb..87e8e0e799d 100644
--- a/frontend/src/metabase/visualizations/Scalar.jsx
+++ b/frontend/src/metabase/visualizations/Scalar.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from "react";
+import { Link } from "react-router";
 import styles from "./Scalar.css";
 
 import Ellipsified from "metabase/components/Ellipsified.jsx";
@@ -166,7 +167,7 @@ export default class Scalar extends Component {
                     {compactScalarValue}
                 </Ellipsified>
                 <Ellipsified className={styles.Title} tooltip={card.name}>
-                    <a className="no-decoration fullscreen-normal-text fullscreen-night-text" href={Urls.card(card.id)}>{card.name}</a>
+                    <Link to={Urls.card(card.id)} className="no-decoration fullscreen-normal-text fullscreen-night-text">{card.name}</Link>
                 </Ellipsified>
             </div>
         );
diff --git a/package.json b/package.json
index 9efe1f4b210..98850bface9 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,12 @@
     "dc": "^2.0.0-beta.25",
     "diff": "^2.2.1",
     "fixed-data-table": "^0.6.0",
-    "history": "1.12.0",
+    "history": "^3.0.0",
     "humanize-plus": "^1.8.1",
     "icepick": "^1.1.0",
     "inflection": "^1.7.1",
     "isomorphic-fetch": "^2.2.1",
+    "js-cookie": "^2.1.2",
     "moment": "^2.12.0",
     "node-libs-browser": "^0.5.3",
     "normalizr": "^2.0.0",
@@ -43,19 +44,21 @@
     "react-dom": "^15.2.1",
     "react-draggable": "^1.1.3",
     "react-onclickout": "^2.0.4",
-    "react-redux": "^4.4.0",
+    "react-redux": "^4.4.5",
     "react-resizable": "^1.0.1",
     "react-retina-image": "^2.0.0",
-    "react-router": "1.0.0",
+    "react-router": "^2.6.0",
+    "react-router-redux": "^4.0.5",
     "react-sortable": "^1.0.1",
     "react-virtualized": "^6.1.2",
     "recompose": "^0.20.2",
-    "redux": "^3.0.4",
+    "redux": "^3.5.2",
     "redux-actions": "^0.9.1",
+    "redux-auth-wrapper": "^0.6.0",
     "redux-form": "^4.2.0",
     "redux-logger": "^2.6.1",
     "redux-promise": "^0.5.0",
-    "redux-router": "^1.0.0-beta4",
+    "redux-router": "^2.1.2",
     "redux-thunk": "^2.0.1",
     "reselect": "^2.0.1",
     "screenfull": "^3.0.0",
diff --git a/resources/frontend_client/index_template.html b/resources/frontend_client/index_template.html
index ebcd9aa00bf..fb9832117d3 100644
--- a/resources/frontend_client/index_template.html
+++ b/resources/frontend_client/index_template.html
@@ -11,10 +11,14 @@
 
         <script type="text/javascript">
          window.MetabaseBootstrap = {{{bootstrap_json}}};
+         console.log("DEBUG_LOAD_COUNT", localStorage.DEBUG_LOAD_COUNT = parseInt(localStorage.DEBUG_LOAD_COUNT || 0) + 1);
         </script>
     </head>
 
-    <body ng-controller="Metabase" ng-view></body>
+    <body>
+        <div id="root" />
+        <div style="display: none;" ng-controller="Metabase" ng-view />
+    </body>
 
     <script type="text/javascript">
      // Load scripts asyncronously after the page has finished loading
diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj
index 35df0c67e05..23e1551bda3 100644
--- a/src/metabase/api/setup.clj
+++ b/src/metabase/api/setup.clj
@@ -111,13 +111,13 @@
      {:title       "Set up email"
       :group       "Get connected"
       :description "Add email credentials so you can more easily invite team members and get updates via Pulses."
-      :link        "/admin/settings/?section=Email"
+      :link        "/admin/settings/email"
       :completed   (email/email-configured?)
       :triggered   :always}
      {:title       "Set Slack credentials"
       :group       "Get connected"
       :description "Does your team use Slack?  If so, you can send automated updates via pulses and ask questions with Metabot."
-      :link        "/admin/settings/?section=Slack"
+      :link        "/admin/settings/slack"
       :completed   (slack/slack-configured?)
       :triggered   :always}
      {:title       "Invite team members"
-- 
GitLab