diff --git a/assets/karma.conf.js b/assets/karma.conf.js
index 427903b22cdb50c155c7024e29a670c7e9f35f27..7e9f7709a3f8c54faac7cc1adc0148d4964b8932 100644
--- a/assets/karma.conf.js
+++ b/assets/karma.conf.js
@@ -36,7 +36,7 @@ module.exports = function (config) {
 
         // list of files / patterns to exclude
         exclude: [
-            'src/scripts/index.js'
+            'src/scripts/site-store.js'
         ],
 
         // preprocess matching files before serving them to the browser
diff --git a/assets/package.json b/assets/package.json
index efb2d886709f79f39081f529172b03737745a3e9..51d66f4cba0667be0e8ace897028142563a4a633 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -13,6 +13,7 @@
     "build:css": "run-p build:css:*",
     "build:css:main": "sass --load-path node_modules/leaflet/dist --load-path node_modules/wdfn-viz/src/stylesheets --load-path node_modules/uswds/src/stylesheets src/styles/main.scss dist/main.css && postcss dist/main.css --map -o dist/main.css",
     "build:css:graph": "sass --load-path node_modules/wdfn-viz/src/stylesheets --load-path node_modules/uswds/src/stylesheets src/styles/graph.scss dist/graph.css && postcss dist/graph.css --map -o dist/graph.css",
+    "build:css:network": "sass --load-path node_modules/leaflet/dist --load-path node_modules/wdfn-viz/src/stylesheets --load-path node_modules/uswds/src/stylesheets src/styles/network.scss dist/network.css && postcss dist/network.css --map -o dist/network.css",
     "build:fonts": "mkdir -p dist/fonts && cp -r node_modules/uswds/src/fonts/* dist/fonts && cp node_modules/@fortawesome/fontawesome-free/webfonts/* dist/fonts",
     "build:images": "mkdir -p dist/img && mkdir -p dist/images && cp -r node_modules/uswds/src/img/* dist/img && cp -r node_modules/wdfn-viz/src/img/* dist/img && cp -r node_modules/leaflet/dist/images/* dist/images && cp -r src/img/* dist/img",
     "build:js": "rollup -c --environment NODE_ENV:production && mkdir -p dist/scripts && cp node_modules/date-time-format-timezone/build/browserified/date-time-format-timezone-complete-min.js dist/scripts",
diff --git a/assets/rollup.config.js b/assets/rollup.config.js
index 586b930ce09b0c49c8060982079fc1f4651eeada..72468a7608c73713facac10ac93d195b21374fee 100644
--- a/assets/rollup.config.js
+++ b/assets/rollup.config.js
@@ -62,11 +62,8 @@ const getBundleConfig = function (src, dest) {
 };
 
 module.exports = [
-
-    getBundleConfig('src/scripts/index.js', 'dist/bundle.js')
-
-    // getBundleConfig('js/bundles/coverage.js', 'dist/scripts/coverage.js'),
-
+    getBundleConfig('src/scripts/index.js', 'dist/bundle.js'),
+    getBundleConfig('src/scripts/networks/index.js', 'dist/network-bundle.js')
 ];
 
 
diff --git a/assets/src/scripts/components/dailyValueHydrograph/index.js b/assets/src/scripts/components/dailyValueHydrograph/index.js
index 522b066c0cdf250c71fddcc33573c7d07766a5aa..a4151c61d1a5d377aeac9f41686b67f9d062b257 100644
--- a/assets/src/scripts/components/dailyValueHydrograph/index.js
+++ b/assets/src/scripts/components/dailyValueHydrograph/index.js
@@ -1,6 +1,6 @@
 import {select} from 'd3-selection';
 
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 import {drawErrorAlert, drawInfoAlert} from '../../d3-rendering/alerts';
 import {drawLoadingIndicator} from '../../d3-rendering/loading-indicator';
diff --git a/assets/src/scripts/components/dailyValueHydrograph/index.spec.js b/assets/src/scripts/components/dailyValueHydrograph/index.spec.js
index acb8c980797d5cdb7a5fe3179932b82f64717792..8cc790bcdd815892646cbf6c9a1e5d69af5ac857 100644
--- a/assets/src/scripts/components/dailyValueHydrograph/index.spec.js
+++ b/assets/src/scripts/components/dailyValueHydrograph/index.spec.js
@@ -1,6 +1,6 @@
 import {select} from 'd3-selection';
 
-import {configureStore, Actions} from '../../store';
+import {configureStore, Actions} from '../../store/site-store';
 
 import {attachToNode} from './index';
 
diff --git a/assets/src/scripts/components/dailyValueHydrograph/time-series-graph.spec.js b/assets/src/scripts/components/dailyValueHydrograph/time-series-graph.spec.js
index c45625a516738c71eeb845e5cc9dc5a8683323df..6fb09676d3deb78a5849e4861178dc7b1374df5d 100644
--- a/assets/src/scripts/components/dailyValueHydrograph/time-series-graph.spec.js
+++ b/assets/src/scripts/components/dailyValueHydrograph/time-series-graph.spec.js
@@ -1,6 +1,6 @@
 import {select} from 'd3-selection';
 
-import {configureStore, Actions} from '../../store';
+import {configureStore, Actions} from '../../store/site-store';
 
 import {drawTimeSeriesGraph} from './time-series-graph';
 
diff --git a/assets/src/scripts/components/dailyValueHydrograph/tooltip.js b/assets/src/scripts/components/dailyValueHydrograph/tooltip.js
index 1e819c732e2bf4ac358421c56d68a16152015c80..433714d55b169a0f6c782b51abb1c20367627e43 100644
--- a/assets/src/scripts/components/dailyValueHydrograph/tooltip.js
+++ b/assets/src/scripts/components/dailyValueHydrograph/tooltip.js
@@ -6,7 +6,7 @@ import {drawCursorSlider} from '../../d3-rendering/cursor-slider';
 import {drawFocusCircles, drawFocusOverlay, drawFocusLine} from '../../d3-rendering/graph-tooltip';
 import {link} from '../../lib/d3-redux';
 import {getObservationsCursorOffset, getCurrentObservationsTimeSeriesUnitOfMeasure} from '../../selectors/observations-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 import {APPROVED, ESTIMATED} from './time-series-graph';
 import {getLayout} from './selectors/layout';
diff --git a/assets/src/scripts/components/dailyValueHydrograph/tooltip.spec.js b/assets/src/scripts/components/dailyValueHydrograph/tooltip.spec.js
index 6162fdb55c4307d97cbb37fac1ae72503e84b438..87bd1de4d598c0c6c3c6695df01c9db2c84d20cf 100644
--- a/assets/src/scripts/components/dailyValueHydrograph/tooltip.spec.js
+++ b/assets/src/scripts/components/dailyValueHydrograph/tooltip.spec.js
@@ -1,6 +1,6 @@
 import {select} from 'd3-selection';
 
-import {configureStore} from '../../store';
+import {configureStore} from '../../store/site-store';
 
 import {drawTooltipFocus, drawTooltipText, drawTooltipCursorSlider} from './tooltip';
 
diff --git a/assets/src/scripts/components/hydrograph/audible.js b/assets/src/scripts/components/hydrograph/audible.js
index 7c7985eb12d3574d53428a21bf4b1bbd8481c088..3897afb24c89a5e3ad3bda9a6430b9e3c1321d64 100644
--- a/assets/src/scripts/components/hydrograph/audible.js
+++ b/assets/src/scripts/components/hydrograph/audible.js
@@ -8,7 +8,7 @@ import { tsCursorPointsSelector } from './cursor';
 import { getMainXScale, getMainYScale } from './scales';
 import config from '../../config';
 import { link } from '../../lib/d3-redux';
-import { Actions } from '../../store';
+import { Actions } from '../../store/site-store';
 
 // Higher tones get lower volume
 const volumeScale = scaleLinear().range([2, .3]);
diff --git a/assets/src/scripts/components/hydrograph/audible.spec.js b/assets/src/scripts/components/hydrograph/audible.spec.js
index b0b5b94b3197135c9acfb348b87abb3f97bedd26..fe9ed994c4e48a43fc6f303743a12340154e6204 100644
--- a/assets/src/scripts/components/hydrograph/audible.spec.js
+++ b/assets/src/scripts/components/hydrograph/audible.spec.js
@@ -1,6 +1,6 @@
 import { select } from 'd3-selection';
 import { audibleUI } from './audible';
-import { configureStore } from '../../store';
+import { configureStore } from '../../store/site-store';
 
 
 const TEST_STATE = {
diff --git a/assets/src/scripts/components/hydrograph/cursor.spec.js b/assets/src/scripts/components/hydrograph/cursor.spec.js
index 4dff5076b432b5af2db3455679bcbc5d5d4028ca..9788297e8c32b109ebe5bfa39fb67ed512178e76 100644
--- a/assets/src/scripts/components/hydrograph/cursor.spec.js
+++ b/assets/src/scripts/components/hydrograph/cursor.spec.js
@@ -1,4 +1,4 @@
-import {Actions, configureStore} from '../../store';
+import {Actions, configureStore} from '../../store/site-store';
 import {tsCursorPointsSelector, cursorOffsetSelector} from './cursor';
 
 let DATA = [12, 13, 14, 15, 16].map(hour => {
diff --git a/assets/src/scripts/components/hydrograph/date-controls.js b/assets/src/scripts/components/hydrograph/date-controls.js
index b342342a379d51ea39b75c7381b9e2c0e7836646..b15dc6a979f481470537528b27ea4c7c00be4ac4 100644
--- a/assets/src/scripts/components/hydrograph/date-controls.js
+++ b/assets/src/scripts/components/hydrograph/date-controls.js
@@ -11,7 +11,7 @@ import {
     getCustomTimeRange,
     getIanaTimeZone
 } from '../../selectors/time-series-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 
 export const drawDateRangeControls = function(elem, store, siteno) {
diff --git a/assets/src/scripts/components/hydrograph/date-controls.spec.js b/assets/src/scripts/components/hydrograph/date-controls.spec.js
index 68db5a9ef007fed449a4015212622c7280d5e4fe..e282ba1249d687915b24b467c8b11d50b25e3d7b 100644
--- a/assets/src/scripts/components/hydrograph/date-controls.spec.js
+++ b/assets/src/scripts/components/hydrograph/date-controls.spec.js
@@ -1,6 +1,6 @@
 import { select } from 'd3-selection';
 
-import { Actions, configureStore } from '../../store';
+import { Actions, configureStore } from '../../store/site-store';
 
 import {drawDateRangeControls} from './date-controls';
 
diff --git a/assets/src/scripts/components/hydrograph/graph-brush.js b/assets/src/scripts/components/hydrograph/graph-brush.js
index 12156e887d9528244a79eb459559d4f85ce58bbb..0244dc8aa54a4ae95fb7711af34327f21daa8eb1 100644
--- a/assets/src/scripts/components/hydrograph/graph-brush.js
+++ b/assets/src/scripts/components/hydrograph/graph-brush.js
@@ -4,7 +4,7 @@ import {createStructuredSelector} from 'reselect';
 
 import {appendXAxis} from '../../d3-rendering/axes';
 import {link} from '../../lib/d3-redux';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 import {getBrushXAxis} from './axes';
 import {currentVariableLineSegmentsSelector} from './drawing-data';
diff --git a/assets/src/scripts/components/hydrograph/graph-brush.spec.js b/assets/src/scripts/components/hydrograph/graph-brush.spec.js
index eb9769131e9b0486149045d6174187f3d81b772d..c1c5fe6df7762209c6534b02865b2ebd4a823333 100644
--- a/assets/src/scripts/components/hydrograph/graph-brush.spec.js
+++ b/assets/src/scripts/components/hydrograph/graph-brush.spec.js
@@ -1,6 +1,6 @@
 import {select} from 'd3-selection';
 
-import{configureStore} from '../../store';
+import{configureStore} from '../../store/site-store';
 
 import {drawGraphBrush} from './graph-brush';
 
diff --git a/assets/src/scripts/components/hydrograph/graph-controls.js b/assets/src/scripts/components/hydrograph/graph-controls.js
index 6d616eebce6dcd914e842671578a6b765c79d066..33742ef257418f0f1e8895e1eb08f1d91f82c3db 100644
--- a/assets/src/scripts/components/hydrograph/graph-controls.js
+++ b/assets/src/scripts/components/hydrograph/graph-controls.js
@@ -1,7 +1,7 @@
 
 import {link} from '../../lib/d3-redux';
 import {getCurrentVariableTimeSeries} from '../../selectors/time-series-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 import {audibleUI} from './audible';
 import {getCurrentVariableMedianStatistics} from '../../selectors/median-statistics-selector';
diff --git a/assets/src/scripts/components/hydrograph/graph-controls.spec.js b/assets/src/scripts/components/hydrograph/graph-controls.spec.js
index 79ca23104765a6c6dbdb6369f25b112c0be7cb8a..f08d3f82cf966f45f1ea901d561ebe45b1a7282c 100644
--- a/assets/src/scripts/components/hydrograph/graph-controls.spec.js
+++ b/assets/src/scripts/components/hydrograph/graph-controls.spec.js
@@ -1,4 +1,4 @@
-import {Actions, configureStore} from '../../store';
+import {Actions, configureStore} from '../../store/site-store';
 import { select } from 'd3-selection';
 import { drawGraphControls } from './graph-controls';
 
diff --git a/assets/src/scripts/components/hydrograph/index.js b/assets/src/scripts/components/hydrograph/index.js
index 1e21cdd1a6277a8b1617303d127fadefda3ea3b9..5c610b3ee722b4603c820df4420325ba2a22df83 100644
--- a/assets/src/scripts/components/hydrograph/index.js
+++ b/assets/src/scripts/components/hydrograph/index.js
@@ -9,7 +9,7 @@ import {drawWarningAlert, drawInfoAlert} from '../../d3-rendering/alerts';
 
 import {link} from '../../lib/d3-redux';
 import {hasAnyTimeSeries, getCurrentParmCd, getVariables} from '../../selectors/time-series-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 import {renderTimeSeriesUrlParams} from '../../url-params';
 
 import {drawDateRangeControls} from './date-controls';
diff --git a/assets/src/scripts/components/hydrograph/index.spec.js b/assets/src/scripts/components/hydrograph/index.spec.js
index 59d1b2a8304f17f18102efdcb46964553a2e20b2..a2203caea328d3ffb39820f99287f2e02be0327c 100644
--- a/assets/src/scripts/components/hydrograph/index.spec.js
+++ b/assets/src/scripts/components/hydrograph/index.spec.js
@@ -1,6 +1,6 @@
 import { select, selectAll } from 'd3-selection';
 import { attachToNode } from './index';
-import { Actions, configureStore } from '../../store';
+import { Actions, configureStore } from '../../store/site-store';
 
 
 const TEST_STATE = {
diff --git a/assets/src/scripts/components/hydrograph/legend.spec.js b/assets/src/scripts/components/hydrograph/legend.spec.js
index dea6bd05991493839006cf66e492bdbd9c51d972..c32d79158e59af9e0e4cf040fc826ce979f77c84 100644
--- a/assets/src/scripts/components/hydrograph/legend.spec.js
+++ b/assets/src/scripts/components/hydrograph/legend.spec.js
@@ -1,7 +1,7 @@
 import {select, selectAll} from 'd3-selection';
 import {legendMarkerRowsSelector, drawTimeSeriesLegend} from './legend';
 import {lineMarker, rectangleMarker, textOnlyMarker} from '../../d3-rendering/markers';
-import {Actions, configureStore} from '../../store';
+import {Actions, configureStore} from '../../store/site-store';
 
 describe('UV: Legend module', () => {
 
diff --git a/assets/src/scripts/components/hydrograph/method-picker.js b/assets/src/scripts/components/hydrograph/method-picker.js
index 91bff95e92de2637ad7938dc68e1d6627ff2300b..2912d271b08ec7bc7699a41372c86e04363040fe 100644
--- a/assets/src/scripts/components/hydrograph/method-picker.js
+++ b/assets/src/scripts/components/hydrograph/method-picker.js
@@ -8,7 +8,7 @@ import {createStructuredSelector} from 'reselect';
 import{link}  from '../../lib/d3-redux';
 import config from '../../config';
 import {getCurrentMethodID, getAllMethodsForCurrentVariable} from '../../selectors/time-series-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 
 import { } from './time-series';
 
diff --git a/assets/src/scripts/components/hydrograph/method-picker.spec.js b/assets/src/scripts/components/hydrograph/method-picker.spec.js
index b2391a7450ca8fbc9ac4606b62335abb5fa348d0..ac6cc38ad500bbe01697190817425fdd7f15f2db 100644
--- a/assets/src/scripts/components/hydrograph/method-picker.spec.js
+++ b/assets/src/scripts/components/hydrograph/method-picker.spec.js
@@ -1,6 +1,6 @@
 import { select } from 'd3-selection';
 
-import { configureStore } from '../../store';
+import { configureStore } from '../../store/site-store';
 
 import { drawMethodPicker } from './method-picker';
 
diff --git a/assets/src/scripts/components/hydrograph/parameters.js b/assets/src/scripts/components/hydrograph/parameters.js
index 13ef13f3838cbebd66ceedbc1a957697e20302a1..dc86280baeaa0235dbc11c5e74bfbc2bcdc4d096 100644
--- a/assets/src/scripts/components/hydrograph/parameters.js
+++ b/assets/src/scripts/components/hydrograph/parameters.js
@@ -5,7 +5,7 @@ import {select} from 'd3-selection';
 import {getVariables, getCurrentVariableID, getTimeSeries} from '../../selectors/time-series-selector';
 
 import config from '../../config';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 import {appendTooltip} from '../../tooltips';
 import {sortedParameters} from '../../utils';
 
diff --git a/assets/src/scripts/components/hydrograph/parameters.spec.js b/assets/src/scripts/components/hydrograph/parameters.spec.js
index 769f2f8eaa4d52d4c6ec2b53fbce0a3f5da63cf8..8bea383ccb61b9add8bdfe4253dccaff2f3ade16 100644
--- a/assets/src/scripts/components/hydrograph/parameters.spec.js
+++ b/assets/src/scripts/components/hydrograph/parameters.spec.js
@@ -1,7 +1,7 @@
 import { select } from 'd3-selection';
 import { scaleLinear } from 'd3-scale';
 import { addSparkLine, plotSeriesSelectTable, availableTimeSeriesSelector } from './parameters';
-import { configureStore} from '../../store';
+import { configureStore} from '../../store/site-store';
 
 
 describe('Parameters module', () => {
diff --git a/assets/src/scripts/components/hydrograph/time-series-graph.spec.js b/assets/src/scripts/components/hydrograph/time-series-graph.spec.js
index 9b279120c3dc9665feea09409ebe11a1afb34e80..3044ffb7624fe29a5dc973866fb68115c2b51cf3 100644
--- a/assets/src/scripts/components/hydrograph/time-series-graph.spec.js
+++ b/assets/src/scripts/components/hydrograph/time-series-graph.spec.js
@@ -1,6 +1,6 @@
 import { select, selectAll } from 'd3-selection';
 
-import {Actions, configureStore} from '../../store';
+import {Actions, configureStore} from '../../store/site-store';
 
 import {drawTimeSeriesGraph} from './time-series-graph';
 
diff --git a/assets/src/scripts/components/hydrograph/tooltip.js b/assets/src/scripts/components/hydrograph/tooltip.js
index fd9bd4cdd33545cd86277de59153b7709d7f5a05..fa56d888b2a8b47fb762bb35f5bfa271caad9502 100644
--- a/assets/src/scripts/components/hydrograph/tooltip.js
+++ b/assets/src/scripts/components/hydrograph/tooltip.js
@@ -10,7 +10,7 @@ import {drawCursorSlider} from '../../d3-rendering/cursor-slider';
 import {drawFocusOverlay, drawFocusCircles, drawFocusLine} from '../../d3-rendering/graph-tooltip';
 import {link} from '../../lib/d3-redux';
 import {getCurrentVariable, getCurrentParmCd} from '../../selectors/time-series-selector';
-import {Actions} from '../../store';
+import {Actions} from '../../store/site-store';
 import {mediaQuery, convertCelsiusToFahrenheit, convertFahrenheitToCelsius} from '../../utils';
 
 import {cursorTimeSelector, tsCursorPointsSelector} from './cursor';
diff --git a/assets/src/scripts/components/hydrograph/tooltip.spec.js b/assets/src/scripts/components/hydrograph/tooltip.spec.js
index 2a60e8c86ed5f3c759435b6471e22b1fdf96ef11..0aced6cbe1d32c81df6ade170cdc376a81fc4187 100644
--- a/assets/src/scripts/components/hydrograph/tooltip.spec.js
+++ b/assets/src/scripts/components/hydrograph/tooltip.spec.js
@@ -1,5 +1,5 @@
 import { select } from 'd3-selection';
-import { Actions, configureStore } from '../../store';
+import { Actions, configureStore } from '../../store/site-store';
 import {drawTooltipText, drawTooltipFocus, tooltipPointsSelector, drawTooltipCursorSlider} from './tooltip';
 
 
diff --git a/assets/src/scripts/components/map/flood-slider.js b/assets/src/scripts/components/map/flood-slider.js
index 54f0403c31999092dfd1db605d58395b1d73996f..e4593af342e34925bc607be6560bcaa8ace882b4 100644
--- a/assets/src/scripts/components/map/flood-slider.js
+++ b/assets/src/scripts/components/map/flood-slider.js
@@ -1,7 +1,7 @@
 import { createStructuredSelector } from 'reselect';
 import { link } from '../../lib/d3-redux';
 import { getFloodStages, getFloodStageHeight, getFloodGageHeightStageIndex, hasFloodData } from '../../selectors/flood-data-selector';
-import { Actions } from '../../store';
+import { Actions } from '../../store/site-store';
 import { appendTooltip } from '../../tooltips';
 
 
diff --git a/assets/src/scripts/components/map/flood-slider.spec.js b/assets/src/scripts/components/map/flood-slider.spec.js
index ed98d624bde910422a0aca12bb49658056aea004..1e0ee66a304e0873725ad673ef39c7bed34520b4 100644
--- a/assets/src/scripts/components/map/flood-slider.spec.js
+++ b/assets/src/scripts/components/map/flood-slider.spec.js
@@ -1,5 +1,5 @@
 import { select } from 'd3-selection';
-import { configureStore } from '../../store';
+import { configureStore } from '../../store/site-store';
 import { floodSlider } from './flood-slider';
 
 
diff --git a/assets/src/scripts/components/map/index.js b/assets/src/scripts/components/map/index.js
index 5a92d4958e8d2350e826dde40bd08c7420ad56a4..634d0f283f2d25f3dd72e730773a9243dbf48342 100644
--- a/assets/src/scripts/components/map/index.js
+++ b/assets/src/scripts/components/map/index.js
@@ -6,7 +6,7 @@ import { link } from '../../lib/d3-redux';
 import config from '../../config';
 import { FLOOD_EXTENTS_ENDPOINT, FLOOD_BREACH_ENDPOINT, FLOOD_LEVEE_ENDPOINT } from '../../web-services/flood-data';
 import { hasFloodData, getFloodExtent, getFloodStageHeight } from '../../selectors/flood-data-selector';
-import { Actions } from '../../store';
+import { Actions } from '../../store/site-store';
 import { floodSlider } from './flood-slider';
 import { createLegendControl, createFIMLegend, createNldiLegend } from './legend';
 import { addNldiLayers} from './nldiMapping';
diff --git a/assets/src/scripts/components/map/index.spec.js b/assets/src/scripts/components/map/index.spec.js
index 33dabbc1456001fc85219866b42c5b1dc2ca9ce2..65025a6617aca93828c428b9d56984550538f35b 100644
--- a/assets/src/scripts/components/map/index.spec.js
+++ b/assets/src/scripts/components/map/index.spec.js
@@ -1,6 +1,6 @@
 import { select } from 'd3-selection';
 import { attachToNode } from './index';
-import { configureStore } from '../../store';
+import { configureStore } from '../../store/site-store';
 
 describe('map module', () => {
     let mapNode;
diff --git a/assets/src/scripts/d3-rendering/legend.spec.js b/assets/src/scripts/d3-rendering/legend.spec.js
index 1ce4f0688dff2f5b6afd8ed8dd0afcd1bffb2136..5ae110edecd2e66727e9882534ad6468b7cb5850 100644
--- a/assets/src/scripts/d3-rendering/legend.spec.js
+++ b/assets/src/scripts/d3-rendering/legend.spec.js
@@ -1,7 +1,7 @@
 import {select, selectAll} from 'd3-selection';
 import {lineMarker, rectangleMarker, textOnlyMarker} from './markers';
 import {drawSimpleLegend} from './legend';
-import {configureStore} from '../store';
+import {configureStore} from '../store/site-store';
 import {drawTimeSeriesLegend} from '../components/dailyValueHydrograph/legend';
 
 describe('Legend module', () => {
diff --git a/assets/src/scripts/index.js b/assets/src/scripts/index.js
index b4df64ff1c658157693cb119160f1b2fbf38ce6c..5dc6716f43342c2266dfcf2922a76fd3d0f771de 100644
--- a/assets/src/scripts/index.js
+++ b/assets/src/scripts/index.js
@@ -6,21 +6,19 @@ import wdfnviz from 'wdfn-viz';
 import {register} from './helpers';
 register();
 
-import {configureStore} from './store';
+import {configureStore} from './store/site-store';
 import {getParamString} from './url-params';
 
 import {attachToNode as EmbedComponent} from './components/embed';
 import {attachToNode as DailyValueHydrographComponent} from './components/dailyValueHydrograph';
 import {attachToNode as HydrographComponent} from './components/hydrograph';
 import {attachToNode as MapComponent} from './components/map';
-import {attachToNode as NetworkMapComponent} from './networks';
 
 const COMPONENTS = {
     embed: EmbedComponent,
     'dv-hydrograph': DailyValueHydrographComponent,
     hydrograph: HydrographComponent,
-    map: MapComponent,
-    'network-map': NetworkMapComponent
+    map: MapComponent
 };
 
 
diff --git a/assets/src/scripts/index.spec.js b/assets/src/scripts/index.spec.js
index b37f248ced2fbd81126b13c743ed9ea902e75ec7..6e1ced577ce329f3181e4d1b6a4aec2115f18ea4 100644
--- a/assets/src/scripts/index.spec.js
+++ b/assets/src/scripts/index.spec.js
@@ -69,7 +69,7 @@ import './selectors/time-series-selector.spec';
 import './store/flood-data-reducer.spec';
 import './store/nldi-data-reducer.spec';
 import './store/flood-state-reducer.spec';
-import './store/index.spec';
+import './store/site-store.spec';
 import './store/observations-data-reducer.spec';
 import './store/observations-state-reducer.spec';
 import './store/series-reducer.spec';
diff --git a/assets/src/scripts/networks/components/index.js b/assets/src/scripts/networks/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b27fdfb72bcbfd1d4d6aab9d3a20731ef02fb1b
--- /dev/null
+++ b/assets/src/scripts/networks/components/index.js
@@ -0,0 +1,103 @@
+import { select } from 'd3-selection';
+import { createStructuredSelector } from 'reselect';
+import { map as createMap, control, layerGroup } from 'leaflet';
+import { TiledMapLayer, basemapLayer } from 'esri-leaflet/src/EsriLeaflet';
+import { link } from '../../lib/d3-redux';
+import config from '../../config';
+
+import { Actions } from '../network-store';
+import { createLegendControl, createNetworkLegend } from './network-legend';
+import { addNetworkLayers} from './network-mapping';
+import { hasNetworkData, getNetworkSites}
+    from '../network-data-selector';
+
+/*
+ * Creates a network map
+ */
+const networkMap = function(node, {extent}, store) {
+
+    let gray = layerGroup();
+    basemapLayer('Gray').addTo(gray);
+
+    if (config.HYDRO_ENDPOINT) {
+        gray.addLayer(new TiledMapLayer({url: config.HYDRO_ENDPOINT,
+            maxZoom: 22,
+            maxNativeZoom: 19}));
+    }
+
+    // Create map on node
+    const map = createMap('network-map', {
+        center: [0, 0],
+        zoom: 1,
+        scrollWheelZoom: false,
+        layers: gray
+    });
+
+    const pExt = JSON.parse(extent);
+    const leafletExtent = [[pExt[3],pExt[2]],[pExt[1],pExt[0]]];
+    map.fitBounds(leafletExtent);
+
+    map.on('focus', () => {
+        map.scrollWheelZoom.enable();
+    });
+    map.on('blur', () => {
+        map.scrollWheelZoom.disable();
+    });
+
+
+    let legendControl = createLegendControl({
+        position: 'bottomright'
+    });
+    legendControl.addTo(map);
+
+    const updateNetworkLayers = function (node, {networkSites}) {
+        addNetworkLayers(map, networkSites);
+    };
+
+    //add additional baselayer
+    var baseLayers = {
+        'Grayscale': gray,
+        'Satellite': basemapLayer('ImageryFirefly')
+    };
+
+    //add layer control
+    control.layers(baseLayers).addTo(map);
+
+    // Add the ESRI World Hydro Reference Overlay
+    if (config.HYDRO_ENDPOINT) {
+        map.addLayer(new TiledMapLayer({url: config.HYDRO_ENDPOINT}));
+    }
+
+    // // Add a marker at the site location
+    // createMarker([latitude, longitude]).addTo(map);
+
+    /*
+     * Creates the NLDI legend if NLDI data is available, otherwise removes the NLDI legend if it exists.
+     * @param {HTMLElement} node - element where the map is rendered
+     * @param {Boolean} isNldiAvailable
+     */
+    const addNetworkLegend = function(node, isNetworkAvailable) {
+        createNetworkLegend(legendControl, isNetworkAvailable);
+    };
+
+    node
+        .call(link(store, addNetworkLegend, hasNetworkData))
+        .call(link(store, updateNetworkLayers, createStructuredSelector({
+            networkSites: getNetworkSites
+        })));
+};
+
+/*
+ * Creates the network map with node and attach it to the Redux store.
+ * @param {Object} store - Redux store
+ * @param {Object} node - DOM element
+ * @param {String} networkcd
+ */
+export const attachToNode = function(store, node, {networkcd, extent}) {
+
+    store.dispatch(Actions.retrieveNetworkData(networkcd));
+
+    select(node)
+        .call(networkMap, {extent}, store);
+};
+
diff --git a/assets/src/scripts/networks/network-legend.js b/assets/src/scripts/networks/components/network-legend.js
similarity index 97%
rename from assets/src/scripts/networks/network-legend.js
rename to assets/src/scripts/networks/components/network-legend.js
index f81419405716d3d9241489bfadeb19c2909f1726..bba46cd92b025237ac645de41e9212bc585e01c7 100644
--- a/assets/src/scripts/networks/network-legend.js
+++ b/assets/src/scripts/networks/components/network-legend.js
@@ -4,9 +4,9 @@
 import { select } from 'd3-selection';
 import { control as createControl, DomUtil, DomEvent } from 'leaflet';
 // import { get } from '../ajax';
-import config from '../config';
-import { mediaQuery } from '../utils';
-import { markerFillColor, markerFillOpacity} from './networkMapping';
+import config from '../../config';
+import { mediaQuery } from '../../utils';
+import { markerFillColor, markerFillOpacity} from './network-mapping';
 
 
 // const fetchLayerLegend = function(layer, defaultName) {
diff --git a/assets/src/scripts/networks/networkMapping.js b/assets/src/scripts/networks/components/network-mapping.js
similarity index 100%
rename from assets/src/scripts/networks/networkMapping.js
rename to assets/src/scripts/networks/components/network-mapping.js
diff --git a/assets/src/scripts/networks/index.js b/assets/src/scripts/networks/index.js
index f2f65aaf17c57955ef53a8aad5d154566c28972e..9caf0ae89d4d26fb501481ab06d02d3ff70320a3 100644
--- a/assets/src/scripts/networks/index.js
+++ b/assets/src/scripts/networks/index.js
@@ -1,103 +1,42 @@
-import { select } from 'd3-selection';
-import { createStructuredSelector } from 'reselect';
-import { map as createMap, control, layerGroup } from 'leaflet';
-import { TiledMapLayer, basemapLayer } from 'esri-leaflet/src/EsriLeaflet';
-import { link } from '../lib/d3-redux';
-import config from '../config';
+import '../polyfills';
 
-import { Actions } from '../store';
-import { createLegendControl, createNetworkLegend } from './network-legend';
-import { addNetworkLayers} from './networkMapping';
-import { hasNetworkData, getNetworkSites}
-    from '../selectors/network-data-selector';
+import wdfnviz from 'wdfn-viz';
 
-/*
- * Creates a network map
- */
-const networkMap = function(node, {extent}, store) {
+// Load misc Javascript helpers for general page interactivity.
+import {register} from '../helpers';
+register();
 
-    let gray = layerGroup();
-    basemapLayer('Gray').addTo(gray);
+import {configureStore} from './network-store';
+import {getParamString} from '../url-params';
 
-    if (config.HYDRO_ENDPOINT) {
-        gray.addLayer(new TiledMapLayer({url: config.HYDRO_ENDPOINT,
-            maxZoom: 22,
-            maxNativeZoom: 19}));
-    }
-
-    // Create map on node
-    const map = createMap('network-map', {
-        center: [0, 0],
-        zoom: 1,
-        scrollWheelZoom: false,
-        layers: gray
-    });
-
-    const pExt = JSON.parse(extent);
-    const leafletExtent = [[pExt[3],pExt[2]],[pExt[1],pExt[0]]];
-    map.fitBounds(leafletExtent);
-
-    map.on('focus', () => {
-        map.scrollWheelZoom.enable();
-    });
-    map.on('blur', () => {
-        map.scrollWheelZoom.disable();
-    });
+import {attachToNode as NetworkMapComponent} from './components';
 
+const COMPONENTS = {
+    'network-map': NetworkMapComponent
+};
 
-    let legendControl = createLegendControl({
-        position: 'bottomright'
+const load = function () {
+    let nodes = document.getElementsByClassName('wdfn-component');
+    let store = configureStore({
+        ui: {
+            windowWidth: window.innerWidth
+        }
     });
-    legendControl.addTo(map);
-
-    const updateNetworkLayers = function (node, {networkSites}) {
-        addNetworkLayers(map, networkSites);
-    };
-
-    //add additional baselayer
-    var baseLayers = {
-        'Grayscale': gray,
-        'Satellite': basemapLayer('ImageryFirefly')
-    };
-
-    //add layer control
-    control.layers(baseLayers).addTo(map);
-
-    // Add the ESRI World Hydro Reference Overlay
-    if (config.HYDRO_ENDPOINT) {
-        map.addLayer(new TiledMapLayer({url: config.HYDRO_ENDPOINT}));
+    for (let node of nodes) {
+        // If options is specified on the node, expect it to be a JSON string.
+        // Otherwise, use the dataset attributes as the component options.
+        const options = node.dataset.options ? JSON.parse(node.dataset.options) : node.dataset;
+        const hashOptions = Object.fromEntries(new window.URLSearchParams(getParamString()));
+        COMPONENTS[node.dataset.component](store, node, Object.assign({}, options, hashOptions));
     }
 
-    // // Add a marker at the site location
-    // createMarker([latitude, longitude]).addTo(map);
-
-    /*
-     * Creates the NLDI legend if NLDI data is available, otherwise removes the NLDI legend if it exists.
-     * @param {HTMLElement} node - element where the map is rendered
-     * @param {Boolean} isNldiAvailable
-     */
-    const addNetworkLegend = function(node, isNetworkAvailable) {
-        createNetworkLegend(legendControl, isNetworkAvailable);
-    };
 
-    node
-        .call(link(store, addNetworkLegend, hasNetworkData))
-        .call(link(store, updateNetworkLayers, createStructuredSelector({
-            networkSites: getNetworkSites
-        })));
 };
 
-/*
- * Creates the network map with node and attach it to the Redux store.
- * @param {Object} store - Redux store
- * @param {Object} node - DOM element
- * @param {String} networkcd
- */
-export const attachToNode = function(store, node, {networkcd, extent}) {
+wdfnviz.main(load);
 
-    store.dispatch(Actions.retrieveNetworkData(networkcd));
 
-    select(node)
-        .call(networkMap, {extent}, store);
-};
 
+// Leaflet expects an exports global to exist - so although we don't use this,
+// just set it to something so it's not undefined.
+export var dummy = true;
\ No newline at end of file
diff --git a/assets/src/scripts/store/network-data-reducer.js b/assets/src/scripts/networks/network-data-reducer.js
similarity index 100%
rename from assets/src/scripts/store/network-data-reducer.js
rename to assets/src/scripts/networks/network-data-reducer.js
diff --git a/assets/src/scripts/store/network-data-reducer.spec.js b/assets/src/scripts/networks/network-data-reducer.spec.js
similarity index 100%
rename from assets/src/scripts/store/network-data-reducer.spec.js
rename to assets/src/scripts/networks/network-data-reducer.spec.js
diff --git a/assets/src/scripts/selectors/network-data-selector.js b/assets/src/scripts/networks/network-data-selector.js
similarity index 100%
rename from assets/src/scripts/selectors/network-data-selector.js
rename to assets/src/scripts/networks/network-data-selector.js
diff --git a/assets/src/scripts/selectors/network-data-selector.spec.js b/assets/src/scripts/networks/network-data-selector.spec.js
similarity index 100%
rename from assets/src/scripts/selectors/network-data-selector.spec.js
rename to assets/src/scripts/networks/network-data-selector.spec.js
diff --git a/assets/src/scripts/web-services/network-data.js b/assets/src/scripts/networks/network-data.js
similarity index 100%
rename from assets/src/scripts/web-services/network-data.js
rename to assets/src/scripts/networks/network-data.js
diff --git a/assets/src/scripts/web-services/network-data.spec.js b/assets/src/scripts/networks/network-data.spec.js
similarity index 100%
rename from assets/src/scripts/web-services/network-data.spec.js
rename to assets/src/scripts/networks/network-data.spec.js
diff --git a/assets/src/scripts/networks/network-store.js b/assets/src/scripts/networks/network-store.js
new file mode 100644
index 0000000000000000000000000000000000000000..dba4db503a35d8d265d9fad9abc9e520fd4c7f51
--- /dev/null
+++ b/assets/src/scripts/networks/network-store.js
@@ -0,0 +1,53 @@
+import {fetchNetworkSites} from '../networks/network-data';
+
+import {uiReducer as ui} from '../store/ui-reducer';
+import {networkDataReducer as networkData} from './network-data-reducer';
+import {configureReduxStore} from '../store';
+
+export const Actions = {
+    retrieveNetworkData(networkCd) {
+        return function(dispatch) {
+            const networkSites = fetchNetworkSites(networkCd);
+
+            return Promise.all( [networkSites]
+            ).then(function(data){
+                const [networkSites] = data;
+                dispatch(Actions.setNetworkFeatures(networkSites));
+            });
+        };
+    },
+    setNetworkFeatures(networkSites) {
+         return {
+            type: 'SET_NETWORK_FEATURES',
+            networkSites
+        };
+    },
+    resizeUI(windowWidth, width) {
+        return {
+            type: 'RESIZE_UI',
+            windowWidth,
+            width
+        };
+    }
+};
+
+const reducers = {
+    ui,
+    networkData
+};
+
+export const configureStore = function(initialState){
+
+     initialState = {
+         networkData: {
+             networkSites: []
+         },
+         ui: {
+             windowWidth: 1024,
+             width: 800
+         },
+         ...initialState
+     };
+
+    return configureReduxStore(initialState, reducers);
+};
\ No newline at end of file
diff --git a/assets/src/scripts/store/index.js b/assets/src/scripts/store/index.js
index afc7284dc5166f6814403639224ed25c2567ad41..1300d454d0b023ef605dfb7b77903887bcaab133 100644
--- a/assets/src/scripts/store/index.js
+++ b/assets/src/scripts/store/index.js
@@ -1,583 +1,39 @@
-
-import find from 'lodash/find';
-import findKey from 'lodash/findKey';
-import last from 'lodash/last';
-import {DateTime} from 'luxon';
-import {applyMiddleware, createStore, combineReducers, compose} from 'redux';
+import {createStore, combineReducers, compose, applyMiddleware} from 'redux';
 import {default as thunk} from 'redux-thunk';
 
-import {normalize} from '../schema';
-import {calcStartTime, sortedParameters} from '../utils';
-
-import {getCurrentParmCd, getCurrentDateRange, hasTimeSeries, getTsRequestKey, getRequestTimeRange,
-    getCustomTimeRange, getIanaTimeZone} from '../selectors/time-series-selector';
-
-import {fetchFloodFeatures, fetchFloodExtent} from '../web-services/flood-data';
-import {getPreviousYearTimeSeries, getTimeSeries, queryWeatherService} from '../web-services/models';
-import {fetchNldiUpstreamSites, fetchNldiDownstreamSites, fetchNldiDownstreamFlow, fetchNldiUpstreamFlow, fetchNldiUpstreamBasin} from '../web-services/nldi-data';
-import {fetchTimeSeries} from '../web-services/observations';
-import {fetchSiteStatistics} from '../web-services/statistics-data';
-import {fetchNetworkSites} from '../web-services/network-data';
-
-import {floodDataReducer as floodData} from './flood-data-reducer';
-import {floodStateReducer as floodState} from './flood-state-reducer';
-import {nldiDataReducer as nldiData} from './nldi-data-reducer';
-import {observationsDataReducer as observationsData} from './observations-data-reducer';
-import {observationsStateReducer as observationsState} from './observations-state-reducer';
-import {seriesReducer as series} from './series-reducer';
-import {statisticsDataReducer as statisticsData} from './statistics-data-reducer';
-import {timeSeriesStateReducer as timeSeriesState} from './time-series-state-reducer';
-import {uiReducer as ui} from './ui-reducer';
-import {networkDataReducer as networkData} from './network-data-reducer';
-
-const GAGE_HEIGHT_CD = '00065';
-/*
- * Helper functions
- */
-const getLatestValue = function(collection, parmCd) {
-    let parmVar = findKey(collection.variables, (varValue) => {
-        return varValue.variableCode.value === parmCd;
-    });
-    let parmTimeSeries = findKey(collection.timeSeries, (ts) => {
-        return ts.variable === parmVar;
-    });
-    let points = parmTimeSeries ? collection.timeSeries[parmTimeSeries].points : [];
-    return points.length ? last(points).value : null;
-};
-
-
-/*
- * @param {Object} timeSeries - keys are time series id
- * @param {Object} variables  - keys are the variable id
- */
-const getCurrentVariableId = function(timeSeries, variables) {
-    const tsVariablesWithData = Object.values(timeSeries)
-        .filter((ts) => ts.points.length)
-        .map((ts) => variables[ts.variable]);
-    const sortedVarsWithData = sortedParameters(tsVariablesWithData);
-    if (sortedVarsWithData.length) {
-        return sortedVarsWithData[0].oid;
-    } else {
-        const sortedVars = sortedParameters(Object.values(variables));
-        return sortedVars.length ? sortedVars[0].oid : '';
-    }
-};
-
-export const Actions = {
-    retrieveLocationTimeZone(latitude, longitude) {
-        return function(dispatch) {
-            return queryWeatherService(latitude, longitude).then(
-                resp => {
-                    const tzIANA = resp.properties.timeZone || null; // set to time zone to null if unavailable
-                    dispatch(Actions.setLocationIanaTimeZone(tzIANA));
-                },
-                () => {
-                    dispatch(Actions.setLocationIanaTimeZone(null));
-                }
-            );
-        };
-    },
-    retrieveTimeSeries(siteno, params=null) {
-        return function (dispatch, getState) {
-            const currentState = getState();
-            const requestKey = getTsRequestKey('current', 'P7D')(currentState);
-            dispatch(Actions.addTimeSeriesLoading([requestKey]));
-            return getTimeSeries({sites: [siteno], params}).then(
-                series => {
-                    const collection = normalize(series, requestKey);
-
-                    // Get the start/end times of this request's range.
-                    const notes = collection.queryInfo[requestKey].notes;
-                    const endTime = notes.requestDT;
-                    const startTime = calcStartTime('P7D', endTime, 'local');
-
-                    // Trigger a call to get last year's data
-                    dispatch(Actions.retrieveCompareTimeSeries(siteno, 'P7D', startTime, endTime));
-
-                    // Update the series data for the 'current' series
-                    dispatch(Actions.addSeriesCollection('current', collection));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-
-                    // Update the application state
-                    dispatch(Actions.toggleTimeSeries('current', true));
-                    const variable = getCurrentVariableId(collection.timeSeries || {}, collection.variables || {});
-                    dispatch(Actions.setCurrentVariable(variable));
-                    dispatch(Actions.setGageHeight(getLatestValue(collection, GAGE_HEIGHT_CD)));
-                },
-                () => {
-                    dispatch(Actions.resetTimeSeries(getTsRequestKey('current', 'P7D')(currentState)));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-
-                    dispatch(Actions.toggleTimeSeries('current', false));
-                }
-            );
-        };
-    },
-    retrieveCompareTimeSeries(site, period, startTime, endTime) {
-        return function (dispatch, getState) {
-            const requestKey = getTsRequestKey('compare', period)(getState());
-            dispatch(Actions.addTimeSeriesLoading([requestKey]));
-            return getPreviousYearTimeSeries({site, startTime, endTime}).then(
-                series => {
-                    const collection = normalize(series, requestKey);
-                    dispatch(Actions.addSeriesCollection(requestKey, collection));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                },
-                () => {
-                    dispatch(Actions.resetTimeSeries(getTsRequestKey('compare', period)(getState())));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                }
-            );
-        };
-    },
-    retrieveMedianStatistics(site) {
-        return function(dispatch) {
-            return fetchSiteStatistics({site, statType: 'median'}).then(
-                stats => {
-                    dispatch(Actions.addMedianStats(stats));
-                }
-            );
-        };
-    },
-
-    retrieveCustomTimePeriodTimeSeries(site, parameterCd, period) {
-        return function(dispatch, getState) {
-            const state = getState();
-            const parmCd = parameterCd;
-            const requestKey = getTsRequestKey('current', 'custom', parmCd)(state);
-            dispatch(Actions.setCurrentDateRange('custom'));
-            dispatch(Actions.addTimeSeriesLoading([requestKey]));
-            return getTimeSeries({sites: [site], params: [parmCd], period: period}).then(
-                series => {
-                    const collection = normalize(series, requestKey);
-                    const variables = Object.values(collection.variables);
-                    const variableToDraw = find(variables, v =>  v.variableCode.value === parameterCd);
-                    dispatch(Actions.setCurrentVariable(variableToDraw.variableCode.variableID));
-                    dispatch(Actions.addSeriesCollection(requestKey, collection));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                },
-                () => {
-                    console.log(`Unable to fetch data for period ${period} and parameter code ${parmCd}`);
-                    dispatch(Actions.addSeriesCollection(requestKey, {}));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                }
-            );
-        };
-    },
-
-    retrieveCustomTimeSeries(site, startTime, endTime, parmCd) {
-        return function(dispatch, getState) {
-            const state = getState();
-            const thisParmCd = parmCd ? parmCd : getCurrentParmCd(state);
-            const requestKey = getTsRequestKey('current', 'custom', thisParmCd)(state);
-
-            dispatch(Actions.setCustomDateRange(startTime, endTime));
-            dispatch(Actions.addTimeSeriesLoading([requestKey]));
-            dispatch(Actions.toggleTimeSeries('median', false));
-            return getTimeSeries({
-                sites: [site],
-                params: [thisParmCd],
-                startDate: startTime,
-                endDate: endTime
-            }).then(
-                series => {
-                    const collection = normalize(series, requestKey);
-                    dispatch(Actions.addSeriesCollection(requestKey, collection));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                },
-                () => {
-                    console.log(`Unable to fetch data for between ${startTime} and ${endTime} and parameter code ${thisParmCd}`);
-                    dispatch(Actions.addSeriesCollection(requestKey, {}));
-                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                }
-            );
-        };
-    },
-    retrieveExtendedTimeSeries(site, period, paramCd=null) {
-        return function(dispatch, getState) {
-            const state = getState();
-            const thisParamCd = paramCd ? paramCd : getCurrentParmCd(state);
-            const requestKey = getTsRequestKey ('current', period, thisParamCd)(state);
-            dispatch(Actions.setCurrentDateRange(period));
-            if (!hasTimeSeries('current', period, thisParamCd)(state)) {
-                dispatch(Actions.addTimeSeriesLoading([requestKey]));
-                const endTime = getRequestTimeRange('current', 'P7D')(state).end;
-                const startTime = calcStartTime(period, endTime);
-                return getTimeSeries({
-                    sites: [site],
-                    params: [thisParamCd],
-                    startDate: startTime,
-                    endDate: endTime
-                }).then(
-                    series => {
-                        const collection = normalize(series, requestKey);
-                        dispatch(Actions.retrieveCompareTimeSeries(site, period, startTime, endTime));
-                        dispatch(Actions.addSeriesCollection(requestKey, collection));
-                        dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                    },
-                    () => {
-                        console.log(`Unable to fetch data for period ${period} and parameter code ${thisParamCd}`);
-                        dispatch(Actions.addSeriesCollection(requestKey, {}));
-                        dispatch(Actions.removeTimeSeriesLoading([requestKey]));
-                    }
-                );
-            } else {
-                return Promise.resolve({});
-            }
-        };
-    },
-    retrieveDailyValueData(monitoringLocationId, timeSeriesId) {
-        return function(dispatch) {
-            return fetchTimeSeries(monitoringLocationId, timeSeriesId)
-                .then(
-                    (data) => {
-                        dispatch(Actions.setObservationsTimeSeries(timeSeriesId, data));
-                    },
-                    () => {
-                        console.log(`Unable to fetch observations time series for ${timeSeriesId}`);
-                    });
-        };
-    },
-    retrieveFloodData(siteno) {
-        return function (dispatch) {
-            const floodFeatures = fetchFloodFeatures(siteno);
-            const floodExtent = fetchFloodExtent(siteno);
-            return Promise.all([floodFeatures, floodExtent]).then((data) => {
-                const [features, extent] = data;
-                const stages = features.map((feature) => feature.attributes.STAGE).sort(function (a, b) {
-                    return a - b;
-                });
-                dispatch(Actions.setFloodFeatures(stages, stages.length ? extent.extent : {}));
-            });
-        };
-    },
-    retrieveNldiData(siteno) {
-        return function (dispatch) {
-            const upstreamFlow = fetchNldiUpstreamFlow(siteno);
-            const downstreamFlow = fetchNldiDownstreamFlow(siteno);
-            const upstreamSites = fetchNldiUpstreamSites(siteno);
-            const downstreamSites = fetchNldiDownstreamSites(siteno);
-            const upstreamBasin = fetchNldiUpstreamBasin(siteno);
-
-            return Promise.all([
-                upstreamFlow, downstreamFlow, upstreamSites, downstreamSites, upstreamBasin
-            ]).then(function(data) {
-               const [upStreamLines, downStreamLines, upStreamPoints, downStreamPoints, upstreamBasin] = data;
-               dispatch(Actions.setNldiFeatures(upStreamLines, downStreamLines, upStreamPoints, downStreamPoints, upstreamBasin));
-            });
-        };
-    },
-    retrieveNetworkData(networkCd) {
-        return function(dispatch) {
-            const networkSites = fetchNetworkSites(networkCd);
-
-            return Promise.all( [networkSites]
-            ).then(function(data){
-                const [networkSites] = data;
-                dispatch(Actions.setNetworkFeatures(networkSites));
-            });
-        };
-    },
-    updateCurrentVariable(siteno, variableID) {
-        return function(dispatch, getState) {
-            dispatch(Actions.setCurrentVariable(variableID));
-            const state = getState();
-            const currentDateRange = getCurrentDateRange(state);
-            if (currentDateRange === 'custom') {
-                const timeRange = getCustomTimeRange(state);
-                dispatch(
-                    Actions.retrieveCustomTimeSeries(siteno, timeRange.startDT, timeRange.endDT));
-            } else {
-                dispatch(Actions.retrieveExtendedTimeSeries(siteno, currentDateRange));
-            }
-        };
-    },
-    startTimeSeriesPlay(maxCursorOffset) {
-        return function (dispatch, getState) {
-            let state = getState().timeSeriesState;
-            if (state.cursorOffset == null || state.cursorOffset >= maxCursorOffset) {
-                dispatch(Actions.setCursorOffset(0));
-            }
-            if (!state.audiblePlayId) {
-                let play = function () {
-                    let newOffset = getState().timeSeriesState.cursorOffset + 15 * 60 * 1000;
-                    if (newOffset > maxCursorOffset) {
-                        dispatch(Actions.stopTimeSeriesPlay());
-                    } else {
-                        dispatch(Actions.setCursorOffset(newOffset));
-                    }
-                };
-                let playId = window.setInterval(play, 10);
-                dispatch(Actions.timeSeriesPlayOn(playId));
-            }
-        };
-    },
-    stopTimeSeriesPlay() {
-        return function(dispatch, getState) {
-            window.clearInterval(getState().timeSeriesState.audiblePlayId);
-            dispatch(Actions.timeSeriesPlayStop());
-        };
-    },
-    timeSeriesPlayOn(playId) {
-        return {
-            type: 'TIME_SERIES_PLAY_ON',
-            playId
-        };
-    },
-    timeSeriesPlayStop() {
-        return {
-            type: 'TIME_SERIES_PLAY_STOP'
-        };
-    },
-    addTimeSeriesLoading(tsKeys) {
-        return {
-            type: 'TIME_SERIES_LOADING_ADD',
-            tsKeys
-        };
-    },
-    removeTimeSeriesLoading(tsKeys) {
-        return {
-            type: 'TIME_SERIES_LOADING_REMOVE',
-            tsKeys
-        };
-    },
-    setFloodFeatures(stages, extent) {
-        return {
-            type: 'SET_FLOOD_FEATURES',
-            stages,
-            extent
-        };
-    },
-    setNldiFeatures(upstreamFlows, downstreamFlows, upstreamSites, downstreamSites, upstreamBasin) {
-        return {
-            type: 'SET_NLDI_FEATURES',
-            upstreamFlows,
-            downstreamFlows,
-            upstreamSites,
-            downstreamSites,
-            upstreamBasin
-        };
-    },
-    setNetworkFeatures(networkSites) {
-         return {
-            type: 'SET_NETWORK_FEATURES',
-            networkSites
-        };
-    },
-    setObservationsTimeSeries(timeSeriesId, data) {
-        return {
-            type: 'SET_OBSERVATIONS_TIME_SERIES',
-            timeSeriesId,
-            data
-        };
-    },
-    toggleTimeSeries(key, show) {
-        return {
-            type: 'TOGGLE_TIME_SERIES',
-            key,
-            show
-        };
-    },
-    addSeriesCollection(key, data) {
-        return {
-            type: 'ADD_TIME_SERIES_COLLECTION',
-            key,
-            data
-        };
-    },
-    resetTimeSeries(key) {
-        return {
-            type: 'RESET_TIME_SERIES',
-            key
-        };
-    },
-    addMedianStats(data) {
-        return {
-            type: 'MEDIAN_STATS_ADD',
-            data
-        };
-    },
-    setCursorOffset(cursorOffset) {
-        return {
-            type: 'SET_CURSOR_OFFSET',
-            cursorOffset
-        };
-    },
-    resizeUI(windowWidth, width) {
-        return {
-            type: 'RESIZE_UI',
-            windowWidth,
-            width
-        };
-    },
-    setHydrographBrushOffset(hydrographBrushOffset) {
-        return {
-            type: 'SET_HYDROGRAPH_BRUSH_OFFSET',
-            hydrographBrushOffset
-        };
-    },
-    clearHydrographBrushOffset() {
-        return {
-            type: 'CLEAR_HYDROGRAPH_BRUSH_OFFSET'
-        };
-    },
-    setCurrentVariable(variableID) {
-        return {
-            type: 'SET_CURRENT_VARIABLE',
-            variableID
-        };
-    },
-    setCurrentMethodID(methodID) {
-        return {
-            type: 'SET_CURRENT_METHOD_ID',
-            methodID
-        };
-    },
-    setCurrentDateRange(period) {
-        return {
-            type: 'SET_CURRENT_DATE_RANGE',
-            period
-        };
-    },
-    setCustomDateRange(startTime, endTime) {
-        return {
-            type: 'SET_CUSTOM_DATE_RANGE',
-            startTime,
-            endTime
-        };
-    },
-    retrieveUserRequestedDataForDateRange(siteno, startTimeStr, endTimeStr) {
-        return function(dispatch, getState) {
-            const state = getState();
-            const locationIanaTimeZone = getIanaTimeZone(state);
-            const startTime = new DateTime.fromISO(startTimeStr,{zone: locationIanaTimeZone}).toMillis();
-            const endTime = new DateTime.fromISO(endTimeStr, {zone: locationIanaTimeZone}).toMillis();
-            return dispatch(Actions.retrieveCustomTimeSeries(siteno, startTime, endTime));
-        };
-    },
-    retrieveDataForDateRange(siteno, startTimeStr, endTimeStr, parmCd) {
-        return function(dispatch, getState) {
-            const state = getState();
-            const locationIanaTimeZone = getIanaTimeZone(state);
-            const startTime = new DateTime.fromISO(startTimeStr,{zone: locationIanaTimeZone}).toMillis();
-            const endTime = new DateTime.fromISO(endTimeStr, {zone: locationIanaTimeZone}).toMillis();
-            return dispatch(Actions.retrieveCustomTimeSeries(siteno, startTime, endTime, parmCd));
-        };
-    },
-    setGageHeightFromStageIndex(index) {
-        return function(dispatch, getState) {
-            const stages = getState().floodData.stages;
-            if (index > -1 && index < stages.length) {
-                dispatch(Actions.setGageHeight(stages[index]));
-            }
-        };
-    },
-    setGageHeight(gageHeight) {
-        return {
-            type: 'SET_GAGE_HEIGHT',
-            gageHeight
-        };
-    },
-    setLocationIanaTimeZone(ianaTimeZone) {
-        return {
-            type: 'LOCATION_IANA_TIME_ZONE_SET',
-            ianaTimeZone
-        };
-    },
-    setCurrentObservationsTimeSeriesId(timeSeriesId) {
-        return {
-            type: 'SET_CURRENT_TIME_SERIES_ID',
-            timeSeriesId
-        };
-    },
-    /*
-     * @param {Number} cursorOffset - difference in epoch time from the start of the graph to the position of of the cursor
-     */
-    setDailyValueCursorOffset(cursorOffset) {
-        return {
-            type: 'SET_DAILY_VALUE_CURSOR_OFFSET',
-            cursorOffset
-        };
-    }
-};
-
-const appReducer = combineReducers({
-    series,
-    observationsData,
-    statisticsData,
-    floodData,
-    nldiData,
-    timeSeriesState,
-    observationsState,
-    floodState,
-    ui,
-    networkData
-});
+const createReducer = function(asyncReducers) {
+  return combineReducers({
+    ...asyncReducers
+  });
+}
 
 const MIDDLEWARES = [thunk];
 
+export const configureReduxStore = function(initialState, reducers) {
+
+  let enhancers;
+  if (window.__REDUX_DEVTOOLS_EXTENSION__) {
+      enhancers = compose(
+          applyMiddleware(...MIDDLEWARES),
+          window.__REDUX_DEVTOOLS_EXTENSION__({serialize: true})
+      );
+  } else {
+      enhancers = applyMiddleware(...MIDDLEWARES);
+  }
 
-export const configureStore = function (initialState) {
-    initialState = {
-        series: {},
-        observationsData: {},
-        floodData: {
-            stages: [],
-            extent: {}
-        },
-        nldiData: {
-            upstreamFlows: [],
-            downstreamFlows: [],
-            upstreamSites: [],
-            downstreamSites: [],
-            upstreamBasin: []
-        },
-        networkData: {
-            networkSites: []
-        },
+  const store = createStore(createReducer(reducers), initialState, enhancers);
 
-        statisticsData: {},
+  // Add a dictionary to keep track of the registered async reducers
+  store.asyncReducers = {}
 
-        timeSeriesState: {
-            showSeries: {
-                current: true,
-                compare: false,
-                median: false
-            },
-            currentDateRange: 'P7D',
-            customTimeRange: null,
-            currentVariableID: null,
-            cursorOffset: null,
-            audiblePlayId: null,
-            loadingTSKeys: [],
-            hydrographBrushOffset: null
-        },
-        observationsState: {
-            cursorOffset: null
-        },
-        floodState: {
-            gageHeight: null
-        },
-        ui : {
-            windowWidth: 1024,
-            width: 800
-        },
-        ...initialState
-    };
+  // Create an inject reducer function
+  // This function adds the async reducer, and creates a new combined reducer
+  store.injectReducer = (key, asyncReducer) => {
+    store.asyncReducers[key] = asyncReducer
+    store.replaceReducer(createReducer(store.asyncReducers));
+  }
 
-    let enhancers;
-    if (window.__REDUX_DEVTOOLS_EXTENSION__) {
-        enhancers = compose(
-            applyMiddleware(...MIDDLEWARES),
-            window.__REDUX_DEVTOOLS_EXTENSION__({serialize: true})
-        );
-    } else {
-        enhancers = applyMiddleware(...MIDDLEWARES);
-    }
+  // Return the modified store
+  return store;
+}
 
-    return createStore(
-        appReducer,
-        initialState,
-        enhancers
-    );
-};
diff --git a/assets/src/scripts/store/site-store.js b/assets/src/scripts/store/site-store.js
new file mode 100644
index 0000000000000000000000000000000000000000..9770760c0cc5ab06a280f5c72a6b5f26156cda8e
--- /dev/null
+++ b/assets/src/scripts/store/site-store.js
@@ -0,0 +1,560 @@
+
+import find from 'lodash/find';
+import findKey from 'lodash/findKey';
+import last from 'lodash/last';
+import {DateTime} from 'luxon';
+
+import {normalize} from '../schema';
+import {calcStartTime, sortedParameters} from '../utils';
+
+import {getCurrentParmCd, getCurrentDateRange, hasTimeSeries, getTsRequestKey, getRequestTimeRange,
+    getCustomTimeRange, getIanaTimeZone} from '../selectors/time-series-selector';
+
+import {fetchFloodFeatures, fetchFloodExtent} from '../web-services/flood-data';
+import {getPreviousYearTimeSeries, getTimeSeries, queryWeatherService} from '../web-services/models';
+import {fetchNldiUpstreamSites, fetchNldiDownstreamSites, fetchNldiDownstreamFlow, fetchNldiUpstreamFlow, fetchNldiUpstreamBasin} from '../web-services/nldi-data';
+import {fetchTimeSeries} from '../web-services/observations';
+import {fetchSiteStatistics} from '../web-services/statistics-data';
+import {fetchNetworkSites} from '../networks/network-data';
+
+import {floodDataReducer as floodData} from './flood-data-reducer';
+import {floodStateReducer as floodState} from './flood-state-reducer';
+import {nldiDataReducer as nldiData} from './nldi-data-reducer';
+import {observationsDataReducer as observationsData} from './observations-data-reducer';
+import {observationsStateReducer as observationsState} from './observations-state-reducer';
+import {seriesReducer as series} from './series-reducer';
+import {statisticsDataReducer as statisticsData} from './statistics-data-reducer';
+import {timeSeriesStateReducer as timeSeriesState} from './time-series-state-reducer';
+import {uiReducer as ui} from './ui-reducer';
+import {configureReduxStore} from './index';
+
+const GAGE_HEIGHT_CD = '00065';
+/*
+ * Helper functions
+ */
+const getLatestValue = function(collection, parmCd) {
+    let parmVar = findKey(collection.variables, (varValue) => {
+        return varValue.variableCode.value === parmCd;
+    });
+    let parmTimeSeries = findKey(collection.timeSeries, (ts) => {
+        return ts.variable === parmVar;
+    });
+    let points = parmTimeSeries ? collection.timeSeries[parmTimeSeries].points : [];
+    return points.length ? last(points).value : null;
+};
+
+
+/*
+ * @param {Object} timeSeries - keys are time series id
+ * @param {Object} variables  - keys are the variable id
+ */
+const getCurrentVariableId = function(timeSeries, variables) {
+    const tsVariablesWithData = Object.values(timeSeries)
+        .filter((ts) => ts.points.length)
+        .map((ts) => variables[ts.variable]);
+    const sortedVarsWithData = sortedParameters(tsVariablesWithData);
+    if (sortedVarsWithData.length) {
+        return sortedVarsWithData[0].oid;
+    } else {
+        const sortedVars = sortedParameters(Object.values(variables));
+        return sortedVars.length ? sortedVars[0].oid : '';
+    }
+};
+
+export const Actions = {
+    retrieveLocationTimeZone(latitude, longitude) {
+        return function(dispatch) {
+            return queryWeatherService(latitude, longitude).then(
+                resp => {
+                    const tzIANA = resp.properties.timeZone || null; // set to time zone to null if unavailable
+                    dispatch(Actions.setLocationIanaTimeZone(tzIANA));
+                },
+                () => {
+                    dispatch(Actions.setLocationIanaTimeZone(null));
+                }
+            );
+        };
+    },
+    retrieveTimeSeries(siteno, params=null) {
+        return function (dispatch, getState) {
+            const currentState = getState();
+            const requestKey = getTsRequestKey('current', 'P7D')(currentState);
+            dispatch(Actions.addTimeSeriesLoading([requestKey]));
+            return getTimeSeries({sites: [siteno], params}).then(
+                series => {
+                    const collection = normalize(series, requestKey);
+
+                    // Get the start/end times of this request's range.
+                    const notes = collection.queryInfo[requestKey].notes;
+                    const endTime = notes.requestDT;
+                    const startTime = calcStartTime('P7D', endTime, 'local');
+
+                    // Trigger a call to get last year's data
+                    dispatch(Actions.retrieveCompareTimeSeries(siteno, 'P7D', startTime, endTime));
+
+                    // Update the series data for the 'current' series
+                    dispatch(Actions.addSeriesCollection('current', collection));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+
+                    // Update the application state
+                    dispatch(Actions.toggleTimeSeries('current', true));
+                    const variable = getCurrentVariableId(collection.timeSeries || {}, collection.variables || {});
+                    dispatch(Actions.setCurrentVariable(variable));
+                    dispatch(Actions.setGageHeight(getLatestValue(collection, GAGE_HEIGHT_CD)));
+                },
+                () => {
+                    dispatch(Actions.resetTimeSeries(getTsRequestKey('current', 'P7D')(currentState)));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+
+                    dispatch(Actions.toggleTimeSeries('current', false));
+                }
+            );
+        };
+    },
+    retrieveCompareTimeSeries(site, period, startTime, endTime) {
+        return function (dispatch, getState) {
+            const requestKey = getTsRequestKey('compare', period)(getState());
+            dispatch(Actions.addTimeSeriesLoading([requestKey]));
+            return getPreviousYearTimeSeries({site, startTime, endTime}).then(
+                series => {
+                    const collection = normalize(series, requestKey);
+                    dispatch(Actions.addSeriesCollection(requestKey, collection));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                },
+                () => {
+                    dispatch(Actions.resetTimeSeries(getTsRequestKey('compare', period)(getState())));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                }
+            );
+        };
+    },
+    retrieveMedianStatistics(site) {
+        return function(dispatch) {
+            return fetchSiteStatistics({site, statType: 'median'}).then(
+                stats => {
+                    dispatch(Actions.addMedianStats(stats));
+                }
+            );
+        };
+    },
+
+    retrieveCustomTimePeriodTimeSeries(site, parameterCd, period) {
+        return function(dispatch, getState) {
+            const state = getState();
+            const parmCd = parameterCd;
+            const requestKey = getTsRequestKey('current', 'custom', parmCd)(state);
+            dispatch(Actions.setCurrentDateRange('custom'));
+            dispatch(Actions.addTimeSeriesLoading([requestKey]));
+            return getTimeSeries({sites: [site], params: [parmCd], period: period}).then(
+                series => {
+                    const collection = normalize(series, requestKey);
+                    const variables = Object.values(collection.variables);
+                    const variableToDraw = find(variables, v =>  v.variableCode.value === parameterCd);
+                    dispatch(Actions.setCurrentVariable(variableToDraw.variableCode.variableID));
+                    dispatch(Actions.addSeriesCollection(requestKey, collection));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                },
+                () => {
+                    console.log(`Unable to fetch data for period ${period} and parameter code ${parmCd}`);
+                    dispatch(Actions.addSeriesCollection(requestKey, {}));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                }
+            );
+        };
+    },
+
+    retrieveCustomTimeSeries(site, startTime, endTime, parmCd) {
+        return function(dispatch, getState) {
+            const state = getState();
+            const thisParmCd = parmCd ? parmCd : getCurrentParmCd(state);
+            const requestKey = getTsRequestKey('current', 'custom', thisParmCd)(state);
+
+            dispatch(Actions.setCustomDateRange(startTime, endTime));
+            dispatch(Actions.addTimeSeriesLoading([requestKey]));
+            dispatch(Actions.toggleTimeSeries('median', false));
+            return getTimeSeries({
+                sites: [site],
+                params: [thisParmCd],
+                startDate: startTime,
+                endDate: endTime
+            }).then(
+                series => {
+                    const collection = normalize(series, requestKey);
+                    dispatch(Actions.addSeriesCollection(requestKey, collection));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                },
+                () => {
+                    console.log(`Unable to fetch data for between ${startTime} and ${endTime} and parameter code ${thisParmCd}`);
+                    dispatch(Actions.addSeriesCollection(requestKey, {}));
+                    dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                }
+            );
+        };
+    },
+    retrieveExtendedTimeSeries(site, period, paramCd=null) {
+        return function(dispatch, getState) {
+            const state = getState();
+            const thisParamCd = paramCd ? paramCd : getCurrentParmCd(state);
+            const requestKey = getTsRequestKey ('current', period, thisParamCd)(state);
+            dispatch(Actions.setCurrentDateRange(period));
+            if (!hasTimeSeries('current', period, thisParamCd)(state)) {
+                dispatch(Actions.addTimeSeriesLoading([requestKey]));
+                const endTime = getRequestTimeRange('current', 'P7D')(state).end;
+                const startTime = calcStartTime(period, endTime);
+                return getTimeSeries({
+                    sites: [site],
+                    params: [thisParamCd],
+                    startDate: startTime,
+                    endDate: endTime
+                }).then(
+                    series => {
+                        const collection = normalize(series, requestKey);
+                        dispatch(Actions.retrieveCompareTimeSeries(site, period, startTime, endTime));
+                        dispatch(Actions.addSeriesCollection(requestKey, collection));
+                        dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                    },
+                    () => {
+                        console.log(`Unable to fetch data for period ${period} and parameter code ${thisParamCd}`);
+                        dispatch(Actions.addSeriesCollection(requestKey, {}));
+                        dispatch(Actions.removeTimeSeriesLoading([requestKey]));
+                    }
+                );
+            } else {
+                return Promise.resolve({});
+            }
+        };
+    },
+    retrieveDailyValueData(monitoringLocationId, timeSeriesId) {
+        return function(dispatch) {
+            return fetchTimeSeries(monitoringLocationId, timeSeriesId)
+                .then(
+                    (data) => {
+                        dispatch(Actions.setObservationsTimeSeries(timeSeriesId, data));
+                    },
+                    () => {
+                        console.log(`Unable to fetch observations time series for ${timeSeriesId}`);
+                    });
+        };
+    },
+    retrieveFloodData(siteno) {
+        return function (dispatch) {
+            const floodFeatures = fetchFloodFeatures(siteno);
+            const floodExtent = fetchFloodExtent(siteno);
+            return Promise.all([floodFeatures, floodExtent]).then((data) => {
+                const [features, extent] = data;
+                const stages = features.map((feature) => feature.attributes.STAGE).sort(function (a, b) {
+                    return a - b;
+                });
+                dispatch(Actions.setFloodFeatures(stages, stages.length ? extent.extent : {}));
+            });
+        };
+    },
+    retrieveNldiData(siteno) {
+        return function (dispatch) {
+            const upstreamFlow = fetchNldiUpstreamFlow(siteno);
+            const downstreamFlow = fetchNldiDownstreamFlow(siteno);
+            const upstreamSites = fetchNldiUpstreamSites(siteno);
+            const downstreamSites = fetchNldiDownstreamSites(siteno);
+            const upstreamBasin = fetchNldiUpstreamBasin(siteno);
+
+            return Promise.all([
+                upstreamFlow, downstreamFlow, upstreamSites, downstreamSites, upstreamBasin
+            ]).then(function(data) {
+               const [upStreamLines, downStreamLines, upStreamPoints, downStreamPoints, upstreamBasin] = data;
+               dispatch(Actions.setNldiFeatures(upStreamLines, downStreamLines, upStreamPoints, downStreamPoints, upstreamBasin));
+            });
+        };
+    },
+    retrieveNetworkData(networkCd) {
+        return function(dispatch) {
+            const networkSites = fetchNetworkSites(networkCd);
+
+            return Promise.all( [networkSites]
+            ).then(function(data){
+                const [networkSites] = data;
+                dispatch(Actions.setNetworkFeatures(networkSites));
+            });
+        };
+    },
+    updateCurrentVariable(siteno, variableID) {
+        return function(dispatch, getState) {
+            dispatch(Actions.setCurrentVariable(variableID));
+            const state = getState();
+            const currentDateRange = getCurrentDateRange(state);
+            if (currentDateRange === 'custom') {
+                const timeRange = getCustomTimeRange(state);
+                dispatch(
+                    Actions.retrieveCustomTimeSeries(siteno, timeRange.startDT, timeRange.endDT));
+            } else {
+                dispatch(Actions.retrieveExtendedTimeSeries(siteno, currentDateRange));
+            }
+        };
+    },
+    startTimeSeriesPlay(maxCursorOffset) {
+        return function (dispatch, getState) {
+            let state = getState().timeSeriesState;
+            if (state.cursorOffset == null || state.cursorOffset >= maxCursorOffset) {
+                dispatch(Actions.setCursorOffset(0));
+            }
+            if (!state.audiblePlayId) {
+                let play = function () {
+                    let newOffset = getState().timeSeriesState.cursorOffset + 15 * 60 * 1000;
+                    if (newOffset > maxCursorOffset) {
+                        dispatch(Actions.stopTimeSeriesPlay());
+                    } else {
+                        dispatch(Actions.setCursorOffset(newOffset));
+                    }
+                };
+                let playId = window.setInterval(play, 10);
+                dispatch(Actions.timeSeriesPlayOn(playId));
+            }
+        };
+    },
+    stopTimeSeriesPlay() {
+        return function(dispatch, getState) {
+            window.clearInterval(getState().timeSeriesState.audiblePlayId);
+            dispatch(Actions.timeSeriesPlayStop());
+        };
+    },
+    timeSeriesPlayOn(playId) {
+        return {
+            type: 'TIME_SERIES_PLAY_ON',
+            playId
+        };
+    },
+    timeSeriesPlayStop() {
+        return {
+            type: 'TIME_SERIES_PLAY_STOP'
+        };
+    },
+    addTimeSeriesLoading(tsKeys) {
+        return {
+            type: 'TIME_SERIES_LOADING_ADD',
+            tsKeys
+        };
+    },
+    removeTimeSeriesLoading(tsKeys) {
+        return {
+            type: 'TIME_SERIES_LOADING_REMOVE',
+            tsKeys
+        };
+    },
+    setFloodFeatures(stages, extent) {
+        return {
+            type: 'SET_FLOOD_FEATURES',
+            stages,
+            extent
+        };
+    },
+    setNldiFeatures(upstreamFlows, downstreamFlows, upstreamSites, downstreamSites, upstreamBasin) {
+        return {
+            type: 'SET_NLDI_FEATURES',
+            upstreamFlows,
+            downstreamFlows,
+            upstreamSites,
+            downstreamSites,
+            upstreamBasin
+        };
+    },
+    setNetworkFeatures(networkSites) {
+         return {
+            type: 'SET_NETWORK_FEATURES',
+            networkSites
+        };
+    },
+    setObservationsTimeSeries(timeSeriesId, data) {
+        return {
+            type: 'SET_OBSERVATIONS_TIME_SERIES',
+            timeSeriesId,
+            data
+        };
+    },
+    toggleTimeSeries(key, show) {
+        return {
+            type: 'TOGGLE_TIME_SERIES',
+            key,
+            show
+        };
+    },
+    addSeriesCollection(key, data) {
+        return {
+            type: 'ADD_TIME_SERIES_COLLECTION',
+            key,
+            data
+        };
+    },
+    resetTimeSeries(key) {
+        return {
+            type: 'RESET_TIME_SERIES',
+            key
+        };
+    },
+    addMedianStats(data) {
+        return {
+            type: 'MEDIAN_STATS_ADD',
+            data
+        };
+    },
+    setCursorOffset(cursorOffset) {
+        return {
+            type: 'SET_CURSOR_OFFSET',
+            cursorOffset
+        };
+    },
+    resizeUI(windowWidth, width) {
+        return {
+            type: 'RESIZE_UI',
+            windowWidth,
+            width
+        };
+    },
+    setHydrographBrushOffset(hydrographBrushOffset) {
+        return {
+            type: 'SET_HYDROGRAPH_BRUSH_OFFSET',
+            hydrographBrushOffset
+        };
+    },
+    clearHydrographBrushOffset() {
+        return {
+            type: 'CLEAR_HYDROGRAPH_BRUSH_OFFSET'
+        };
+    },
+    setCurrentVariable(variableID) {
+        return {
+            type: 'SET_CURRENT_VARIABLE',
+            variableID
+        };
+    },
+    setCurrentMethodID(methodID) {
+        return {
+            type: 'SET_CURRENT_METHOD_ID',
+            methodID
+        };
+    },
+    setCurrentDateRange(period) {
+        return {
+            type: 'SET_CURRENT_DATE_RANGE',
+            period
+        };
+    },
+    setCustomDateRange(startTime, endTime) {
+        return {
+            type: 'SET_CUSTOM_DATE_RANGE',
+            startTime,
+            endTime
+        };
+    },
+    retrieveUserRequestedDataForDateRange(siteno, startTimeStr, endTimeStr) {
+        return function(dispatch, getState) {
+            const state = getState();
+            const locationIanaTimeZone = getIanaTimeZone(state);
+            const startTime = new DateTime.fromISO(startTimeStr,{zone: locationIanaTimeZone}).toMillis();
+            const endTime = new DateTime.fromISO(endTimeStr, {zone: locationIanaTimeZone}).toMillis();
+            return dispatch(Actions.retrieveCustomTimeSeries(siteno, startTime, endTime));
+        };
+    },
+    retrieveDataForDateRange(siteno, startTimeStr, endTimeStr, parmCd) {
+        return function(dispatch, getState) {
+            const state = getState();
+            const locationIanaTimeZone = getIanaTimeZone(state);
+            const startTime = new DateTime.fromISO(startTimeStr,{zone: locationIanaTimeZone}).toMillis();
+            const endTime = new DateTime.fromISO(endTimeStr, {zone: locationIanaTimeZone}).toMillis();
+            return dispatch(Actions.retrieveCustomTimeSeries(siteno, startTime, endTime, parmCd));
+        };
+    },
+    setGageHeightFromStageIndex(index) {
+        return function(dispatch, getState) {
+            const stages = getState().floodData.stages;
+            if (index > -1 && index < stages.length) {
+                dispatch(Actions.setGageHeight(stages[index]));
+            }
+        };
+    },
+    setGageHeight(gageHeight) {
+        return {
+            type: 'SET_GAGE_HEIGHT',
+            gageHeight
+        };
+    },
+    setLocationIanaTimeZone(ianaTimeZone) {
+        return {
+            type: 'LOCATION_IANA_TIME_ZONE_SET',
+            ianaTimeZone
+        };
+    },
+    setCurrentObservationsTimeSeriesId(timeSeriesId) {
+        return {
+            type: 'SET_CURRENT_TIME_SERIES_ID',
+            timeSeriesId
+        };
+    },
+    /*
+     * @param {Number} cursorOffset - difference in epoch time from the start of the graph to the position of of the cursor
+     */
+    setDailyValueCursorOffset(cursorOffset) {
+        return {
+            type: 'SET_DAILY_VALUE_CURSOR_OFFSET',
+            cursorOffset
+        };
+    }
+};
+
+const reducers = {
+    series,
+    observationsData,
+    statisticsData,
+    floodData,
+    nldiData,
+    timeSeriesState,
+    observationsState,
+    floodState,
+    ui
+};
+
+export const configureStore = function(initialState){
+
+     initialState = {
+         series: {},
+         observationsData: {},
+         floodData: {
+             stages: [],
+             extent: {}
+         },
+         nldiData: {
+             upstreamFlows: [],
+             downstreamFlows: [],
+             upstreamSites: [],
+             downstreamSites: [],
+             upstreamBasin: []
+         },
+         statisticsData: {},
+
+         timeSeriesState: {
+             showSeries: {
+                 current: true,
+                 compare: false,
+                 median: false
+             },
+             currentDateRange: 'P7D',
+             customTimeRange: null,
+             currentVariableID: null,
+             cursorOffset: null,
+             audiblePlayId: null,
+             loadingTSKeys: [],
+             hydrographBrushOffset: null
+         },
+         observationsState: {
+             cursorOffset: null
+         },
+         floodState: {
+             gageHeight: null
+         },
+         ui: {
+             windowWidth: 1024,
+             width: 800
+         },
+         ...initialState
+     };
+
+    return configureReduxStore(initialState, reducers);
+};
diff --git a/assets/src/scripts/store/index.spec.js b/assets/src/scripts/store/site-store.spec.js
similarity index 99%
rename from assets/src/scripts/store/index.spec.js
rename to assets/src/scripts/store/site-store.spec.js
index 0f2b87786663f69dc6b9d89ec90c74a166a8405b..49610eaa2a9bf27f0be6943a72ad0e455bce3a08 100644
--- a/assets/src/scripts/store/index.spec.js
+++ b/assets/src/scripts/store/site-store.spec.js
@@ -1,4 +1,4 @@
-import { Actions, configureStore } from './index';
+import { Actions, configureStore } from './site-store';
 import { MOCK_RDB as MOCK_STATS_DATA } from '../web-services/statistics-data.spec.js';
 
 describe('Redux store', () => {
diff --git a/assets/src/scripts/url-params.spec.js b/assets/src/scripts/url-params.spec.js
index df8f0853ec4f4d1c74dcf7fd5fe3c717d2d4dfc1..464ab5a7d00451baafaee60e635cd2c9c75a34e2 100644
--- a/assets/src/scripts/url-params.spec.js
+++ b/assets/src/scripts/url-params.spec.js
@@ -1,5 +1,5 @@
 
-import {configureStore, Actions} from './store';
+import {configureStore, Actions} from './store/site-store';
 import {getParamString, renderTimeSeriesUrlParams} from './url-params';
 
 describe('url-params module', () => {
diff --git a/assets/src/styles/main.scss b/assets/src/styles/main.scss
index b56b86d86f99098dc27f1eebd350e7f12a7f47f3..49b052ea97a05f86ef895e1406b523e038bf40ce 100644
--- a/assets/src/styles/main.scss
+++ b/assets/src/styles/main.scss
@@ -1,6 +1,5 @@
 @import 'leaflet';
-@import '../../node_modules/markercluster-iow/dist/_MarkerCluster.scss';
-@import '../../node_modules/markercluster-iow/dist/_MarkerCluster.Default.scss';
+
 @import './common';
 
 $fa-font-path: './fonts' !default;
@@ -32,7 +31,7 @@ $fa-font-path: './fonts' !default;
   }
 
   &[data-component='network-map']{
-    @import './networks/networkmap';
+    @import 'network';
     margin-bottom: 1em;
   }
 }
diff --git a/assets/src/styles/network.scss b/assets/src/styles/network.scss
new file mode 100644
index 0000000000000000000000000000000000000000..13f75da41140b88f6072366b48c124e713419342
--- /dev/null
+++ b/assets/src/styles/network.scss
@@ -0,0 +1,147 @@
+@import 'leaflet';
+@import 'common';
+
+$fa-font-path: './fonts' !default;
+@import '../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome';
+@import '../../node_modules/@fortawesome/fontawesome-free/scss/brands';
+@import '../../node_modules/@fortawesome/fontawesome-free/scss/solid';
+
+@import 'partials/script';
+@import 'partials/slider';
+@import 'partials/social-share';
+@import 'partials/tooltip';
+
+@import '../../node_modules/markercluster-iow/dist/MarkerCluster';
+@import '../../node_modules/markercluster-iow/dist/MarkerCluster.Default';
+
+.wdfn-component {
+  &[data-component='network-map'] {
+    #network-map {
+      height: 350px;
+      margin: 1em 0;
+
+      .legend {
+        padding: 2px;
+        background-color: white;
+
+        .legend-expand-container {
+          font-size: 14px;
+          font-weight: bold;
+
+          span {
+            padding-right: 2px;
+          }
+
+          .legend-expand {
+            width: auto;
+            float: right;
+            padding: 0;
+            margin: 0;
+            color: black;
+            border: none;
+            border-radius: 0;
+            box-shadow: none;
+
+            i {
+              padding: 2px;
+              background-color: color('base-lighter');
+            }
+          }
+        }
+
+        .legend-list-container {
+          #network-legend-list {
+            span {
+              margin-left: 2px;
+            }
+
+            img {
+              padding-top: 2px;
+              width: 15px;
+            }
+          }
+
+          #fim-legend-list {
+            span {
+              margin-left: 2px;
+            }
+
+            img {
+              padding-top: 2px;
+              width: 15px;
+              height: 13px;
+            }
+          }
+        }
+
+      }
+
+      .leaflet-control-layers-selector {
+        left: 0;
+        position: relative;
+        display: inline;
+        height: auto;
+        width: auto;
+        outline-style: none;
+      }
+
+      label {
+        margin: auto;
+      }
+    }
+    margin-bottom: 1em;
+  }
+}
+
+/* This fixes an issue with Firefox not hiding long divs. I opened https://github.com/uswds/uswds/issues/2380 .
+ * If it gets addressed this can be removed.
+ */
+.usa-sr-only {
+  overflow: hidden;
+}
+
+body {
+  #main-content {
+    margin-top: 1em;
+    margin-bottom: 1em;
+    padding: 0 1em;
+  }
+
+  .content-container {
+    margin-left: auto;
+    margin-right: auto;
+    @include u-maxw('desktop');
+  }
+
+  #wdfn-alert-banner input:checked ~ * {
+    display: none;
+  }
+
+  #wdfn-alert-banner {
+    background-color: color('accent-cool-lighter');
+
+    .wdfn-alert-body {
+      padding-top: 0.5em;
+      padding-bottom: 0.5em;
+      padding-right: 1em;
+      padding-left: 1em;
+      margin-top: 0;
+
+      .close {
+        float: right;
+        cursor: pointer;
+        margin-top: -5px;
+        margin-right: -10px;
+      }
+
+      .div {
+        font-size: 0.9em;
+        @include at-media('tablet') {
+          font-size: 1em;
+        }
+        padding-left: 1em;
+      }
+    }
+
+  }
+}
diff --git a/assets/src/styles/networks/_networkmap.scss b/assets/src/styles/networks/_networkmap.scss
deleted file mode 100644
index ba284668bd4dd391896745e33c3c3ea95a9a79e1..0000000000000000000000000000000000000000
--- a/assets/src/styles/networks/_networkmap.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-#network-map {
-  height: 350px;
-  margin: 1em 0;
-
-  .legend {
-    padding: 2px;
-    background-color: white;
-
-    .legend-expand-container {
-      font-size: 14px;
-      font-weight: bold;
-
-      span {
-        padding-right: 2px;
-      }
-
-      .legend-expand {
-        width: auto;
-        float: right;
-        padding: 0;
-        margin: 0;
-        color: black;
-        border: none;
-        border-radius: 0;
-        box-shadow: none;
-
-        i {
-          padding: 2px;
-          background-color: color('base-lighter');
-        }
-      }
-    }
-
-    .legend-list-container {
-      #network-legend-list {
-        span {
-          margin-left: 2px;
-        }
-
-        img {
-          padding-top: 2px;
-          width: 15px;
-        }
-      }
-
-      #fim-legend-list {
-        span {
-          margin-left: 2px;
-        }
-
-        img {
-          padding-top: 2px;
-          width: 15px;
-          height: 13px;
-        }
-      }
-    }
-
-  }
-
-  .leaflet-control-layers-selector {
-    left: 0;
-    position: relative;
-    display: inline;
-    height: auto;
-    width: auto;
-    outline-style: none;
-  }
-
-  label {
-    margin: auto;
-  }
-}
\ No newline at end of file
diff --git a/graph-server/Dockerfile b/graph-server/Dockerfile
index 83a837f1631c9fb694e8be30025286cecbb95ffb..ea1837d4ce9c1c4e8038c3b3d13027db39d40f03 100644
--- a/graph-server/Dockerfile
+++ b/graph-server/Dockerfile
@@ -34,4 +34,4 @@ USER grapher
 
 EXPOSE 2929
 
-CMD DEBUG=${DEBUG} STATIC_ROOT=${STATIC_URL} OGC_SITE_ENDPOINT=${OGC_SITE_ENDPOINT} node src/index.js
\ No newline at end of file
+CMD DEBUG=${DEBUG} STATIC_ROOT=${STATIC_URL} OGC_SITE_ENDPOINT=${OGC_SITE_ENDPOINT} node src/site-store.js
\ No newline at end of file
diff --git a/graph-server/README.md b/graph-server/README.md
index 73c6e1dbf1f5bc22445042277e6a1bf24aea011d..0c9437bd3ad5f1737e307059dc0cd736754d0b24 100644
--- a/graph-server/README.md
+++ b/graph-server/README.md
@@ -19,7 +19,7 @@ one representing the time period specified and the second representing the same
 
 ## Running the server
 
-The entrypoint is `src/index.js`, which accepts the following environment
+The entrypoint is `src/site-store.js`, which accepts the following environment
 variables as arguments:
 
 - NODE_PORT: Port to run http server on. Default 2929.
@@ -30,7 +30,7 @@ variables as arguments:
 For example:
 
 ```bash
-% NODE_PORT=80 node src/index.js
+% NODE_PORT=80 node src/site-store.js
 ```
 
 Alternatively, if you want to use defaults as well as add DEBUG just use
diff --git a/wdfn-server/waterdata/templates/base_network.html b/wdfn-server/waterdata/templates/base_network.html
new file mode 100644
index 0000000000000000000000000000000000000000..3731b8242c43e230602080a6a08a9f27a8d82aa6
--- /dev/null
+++ b/wdfn-server/waterdata/templates/base_network.html
@@ -0,0 +1,21 @@
+{% extends 'base_plain_network.html' %}
+
+{% block body %}
+    <section role="banner">
+        {% include 'partials/header.html' %}
+        <header id="wdfn-alert-banner">
+            <input type="checkbox" id="wdfn-alert-dismiss" hidden/>
+            <div class="wdfn-alert-body content-container">
+                <label title="Close" class="close" for="wdfn-alert-dismiss"><i class="fas fa-times"></i></label>
+                <div>
+                    Beta release. Use <a class="usa-link" target="_blank" href="https://water.usgs.gov/contact/gsanswers?pemail=gs-w_water_data_for_the_nation&subject=Water%20Data%20for%20the%20Nation%20Feedback&viewnote=%3CH1%3EUSGS+WDFN+TNG+Feedback%3C/H1%3E">this form</a> to provide feedback.
+                </div>
+            </div>
+        </header>
+    </section>
+    <main id="main-content" class="grid-container content-container usa-prose">
+        {% block content %}{% endblock %}
+    </main>
+    {% include 'partials/footer.html' %}
+    {% block extra_body %}{% endblock %}
+{% endblock %}
diff --git a/wdfn-server/waterdata/templates/base_plain_network.html b/wdfn-server/waterdata/templates/base_plain_network.html
new file mode 100644
index 0000000000000000000000000000000000000000..be4843f4ff04b6c768056da9d275265876c3b80c
--- /dev/null
+++ b/wdfn-server/waterdata/templates/base_plain_network.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <meta name="format-detection" content="telephone=no">
+        {% block extra_head_tags %}{% endblock %}
+
+        {% include 'partials/google_analytics.html' %}
+
+        <link rel="shortcut icon" type="image/ico" href="{{ 'img/usgs_favicon.ico' | asset_url }}">
+        <link rel="stylesheet" href="{{ 'network.css' | asset_url }}">
+        <script type="text/javascript">
+            var CONFIG = {
+                'SERVICE_ROOT': '{{ config.SERVICE_ROOT }}/nwis',
+                'PAST_SERVICE_ROOT': '{{ config.PAST_SERVICE_ROOT }}/nwis',
+                'OBSERVATIONS_ENDPOINT': '{{ config.OBSERVATIONS_ENDPOINT }}',
+                'HYDRO_ENDPOINT': '{{ config.HYDRO_ENDPOINT }}',
+                'FIM_GIS_ENDPOINT': '{{ config.FIM_GIS_ENDPOINT }}',
+                'FIM_ENDPOINT': '{{ config.FIM_ENDPOINT }}',
+                'STATIC_URL': '{{ config.STATIC_ROOT }}',
+                'TIMESERIES_AUDIO_ENABLED': '{{ config.TIMESERIES_AUDIO_ENABLED}}' === 'True' ? true : false,
+                'MULTIPLE_TIME_SERIES_METADATA_SELECTOR_ENABLED': '{{ config.MULTIPLE_TIME_SERIES_METADATA_SELECTOR_ENABLED }}' === 'True' ? true : false,
+                'WEATHER_SERVICE_ROOT': '{{ config.WEATHER_SERVICE_ROOT }}',
+                'CITIES_ENDPOINT': '{{ config.CITIES_ENDPOINT }}',
+                'NLDI_SERVICES_ENDPOINT': '{{ config.NLDI_SERVICES_ENDPOINT }}',
+                'NLDI_SERVICES_DISTANCE': '{{ config.NLDI_SERVICES_DISTANCE }}',
+                'NETWORK_ENDPOINT': '{{ config.NETWORK_ENDPOINT }}'
+            };
+        </script>
+        <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
+        <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>
+        <script type="text/javascript">
+            if (!!document.documentMode) {
+                document.write('<script src="{{ "scripts/date-time-format-timezone-complete-min.js" | asset_url }}"><\/script>');
+            }
+        </script>
+
+
+        <script async src="{{ 'network-bundle.js' | asset_url }}"></script>
+        {% block page_script %}{% endblock %}
+        <!-- Google Tag Manager --><script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-TKQR8KP');</script>
+        <title>
+            {% block title %}{% if page_title %}{{ page_title }} - {% endif %}USGS Water Data for the Nation{% endblock %}
+        </title>
+    </head>
+    <body {% if body_id %}id="{{ body_id }}"{% endif %}>
+        <script>document.body.className += ' js';</script>
+        {% block body %}{% endblock %}
+    </body>
+    {% if config.LIVE_RELOAD_PATH %}
+        <script type="text/javascript">
+            // <![CDATA[  <-- For SVG support
+            if ('WebSocket' in window) {
+                (function() {
+                    function refreshCSS() {
+                        var sheets = [].slice.call(document.getElementsByTagName("link"));
+                        var head = document.getElementsByTagName("head")[0];
+                        for (var i = 0; i < sheets.length; ++i) {
+                            var elem = sheets[i];
+                            head.removeChild(elem);
+                            var rel = elem.rel;
+                            if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
+                                var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
+                                elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
+                            }
+                            head.appendChild(elem);
+                        }
+                    }
+                    var socket = new WebSocket('{{ config.LIVE_RELOAD_PATH }}');
+                    socket.onmessage = function(msg) {
+                        if (msg.data == 'reload') window.location.reload();
+                        else if (msg.data == 'refreshcss') refreshCSS();
+                    };
+                    console.log('Live reload enabled.');
+                })();
+            }
+            // ]]>
+        </script>
+    {% endif %}
+    {% block extra_html %}{% endblock %}
+    <!-- Google Tag Manager (noscript) --><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TKQR8KP" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
+</html>
diff --git a/wdfn-server/waterdata/templates/networks.html b/wdfn-server/waterdata/templates/networks.html
index 499fd7d599c532cd8813d4c1907c3cf8cf5c115f..0a886dfc5e12415b4bba1a01ce1ba3015e7502ff 100644
--- a/wdfn-server/waterdata/templates/networks.html
+++ b/wdfn-server/waterdata/templates/networks.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_network.html' %}
 
 {% if network_cd %}{% set page_title = 'Networks' %}{% endif %}