diff --git a/support/nix/build-shake.nix b/support/nix/build-shake.nix
index 3568561d9..eb11a3b87 100644
--- a/support/nix/build-shake.nix
+++ b/support/nix/build-shake.nix
@@ -36,7 +36,7 @@ stdenv.mkDerivation {
   propagatedBuildInputs = [ lua5_3 gmp ];
 
   buildPhase = ''
-  ghc -o ${main} app/${main} -threaded -rtsopts -iapp -O2 -split-sections
+  ghc -o ${main} app/${main} -threaded -rtsopts -iapp -O2 -split-sections -DNODE_BIN_PATH="\"${nodeDependencies}/bin\""
   '';
 
   installPhase = ''
diff --git a/support/shake/app/Shake/Utils.hs b/support/shake/app/Shake/Utils.hs
index e145ae69e..c8f6efef9 100644
--- a/support/shake/app/Shake/Utils.hs
+++ b/support/shake/app/Shake/Utils.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 module Shake.Utils
   ( nodeCommand
   , readJSONFile
@@ -7,10 +8,21 @@ import Data.Aeson
 
 import Development.Shake
 
--- | Invoke a command either from `PATH` or from `node_modules/.bin`
+-- | Invoke a Node command. On Nix builds (more generally, if the
+-- @NODE_BIN_PATH@ preprocessor macro is set while compiling), this will
+-- look for the command in a statically-known path. Otherwise, it'll try
+-- from @node_modules/.bin@ or your @PATH@.
 nodeCommand :: CmdResult r => [CmdOption] -> String -> [String] -> Action r
+#ifdef NODE_BIN_PATH
+
+nodeCommand opts path = command opts ( NODE_BIN_PATH ++ "/" ++ path )
+
+#else
+
 nodeCommand opts = command (opts ++ [AddPath [] ["node_modules/.bin"]])
 
+#endif
+
 -- | Read and decode JSON from a file, tracking it as a dependency.
 readJSONFile :: FromJSON b => FilePath -> Action b
 readJSONFile path = need [path] >> liftIO (eitherDecodeFileStrict' path) >>= either fail pure