diff --git a/lib/manager/npm/extract/index.js b/lib/manager/npm/extract/index.js
index e0f9902dce22c282b42c8232cff5ad29095d9069..bf147185dd9789392cab233bd183cfb9851455c2 100644
--- a/lib/manager/npm/extract/index.js
+++ b/lib/manager/npm/extract/index.js
@@ -2,6 +2,7 @@ const path = require('path');
 const upath = require('upath');
 const { getLockedVersions } = require('./locked-versions');
 const { detectMonorepos } = require('./monorepo');
+const { mightBeABrowserLibrary } = require('./type');
 
 module.exports = {
   extractDependencies,
@@ -22,6 +23,9 @@ async function extractDependencies(content, packageFile) {
   const packageJsonName = packageJson.name;
   const packageJsonVersion = packageJson.version;
   const yarnWorkspacesPackages = packageJson.workspaces;
+  const packageJsonType = mightBeABrowserLibrary(packageJson)
+    ? 'library'
+    : 'app';
 
   const lockFiles = {
     yarnLock: 'yarn.lock',
@@ -106,6 +110,7 @@ async function extractDependencies(content, packageFile) {
     deps,
     packageJsonName,
     packageJsonVersion,
+    packageJsonType,
     npmrc,
     ...lockFiles,
     lernaDir,
diff --git a/lib/manager/npm/extract/type.js b/lib/manager/npm/extract/type.js
new file mode 100644
index 0000000000000000000000000000000000000000..111498e1fdc2557acc6478782853f0325b4b28da
--- /dev/null
+++ b/lib/manager/npm/extract/type.js
@@ -0,0 +1,18 @@
+module.exports = {
+  mightBeABrowserLibrary,
+};
+
+function mightBeABrowserLibrary(packageJson) {
+  // return true unless we're sure it's not a browser library
+  if (packageJson.private === true) {
+    // it's not published
+    return false;
+  }
+  if (packageJson.main === undefined) {
+    // it can't be required
+    return false;
+  }
+  // TODO: how can we know if it's a node.js library only, and not browser?
+  // Otherwise play it safe and return true
+  return true;
+}
diff --git a/lib/workers/repository/process/fetch.js b/lib/workers/repository/process/fetch.js
index ae7f5edc29bea0248ebddcdda872ab0bee2abc68..834f127b2f0e4ec665fbeedba366bb876ba59722 100644
--- a/lib/workers/repository/process/fetch.js
+++ b/lib/workers/repository/process/fetch.js
@@ -12,7 +12,7 @@ module.exports = {
 async function fetchDepUpdates(packageFileConfig, dep) {
   /* eslint-disable no-param-reassign */
   const { manager, packageFile } = packageFileConfig;
-  const { depName, currentVersion } = dep;
+  const { depType, depName, currentVersion } = dep;
   let depConfig = mergeChildConfig(packageFileConfig, dep);
   depConfig = applyPackageRules(depConfig);
   dep.updates = [];
@@ -32,6 +32,18 @@ async function fetchDepUpdates(packageFileConfig, dep) {
     logger.debug({ depName: dep.depName }, 'Dependency is disabled');
     dep.skipReason = 'disabled';
   } else {
+    if (depConfig.pinVersions === null && !depConfig.upgradeInRange) {
+      if (depType === 'devDependencies') {
+        // Always pin devDependencies
+        logger.debug({ depName }, 'Pinning devDependency');
+        depConfig.pinVersions = true;
+      }
+      if (depType === 'dependencies' && depConfig.packageJsonType === 'app') {
+        // Pin dependencies if we're pretty sure it's not a browser library
+        logger.debug('Pinning app dependency');
+        depConfig.pinVersions = true;
+      }
+    }
     dep.updates = await getPackageUpdates(manager, depConfig);
     logger.debug({
       packageFile,
diff --git a/test/manager/npm/extract/__snapshots__/index.spec.js.snap b/test/manager/npm/extract/__snapshots__/index.spec.js.snap
index 3323d1bbcb06bb11458e1553881084558486aab0..0bf7ed26cfbdd7eb192fdfc6470bee31b1fcd22b 100644
--- a/test/manager/npm/extract/__snapshots__/index.spec.js.snap
+++ b/test/manager/npm/extract/__snapshots__/index.spec.js.snap
@@ -60,6 +60,7 @@ Object {
   "npmLock": undefined,
   "npmrc": undefined,
   "packageJsonName": "renovate",
+  "packageJsonType": "app",
   "packageJsonVersion": "1.0.0",
   "pnpmShrinkwrap": undefined,
   "yarnLock": "yarn.lock",
@@ -127,6 +128,7 @@ Object {
   "npmLock": undefined,
   "npmrc": undefined,
   "packageJsonName": "renovate",
+  "packageJsonType": "app",
   "packageJsonVersion": "1.0.0",
   "pnpmShrinkwrap": undefined,
   "yarnLock": undefined,
@@ -194,6 +196,7 @@ Object {
   "npmLock": undefined,
   "npmrc": undefined,
   "packageJsonName": "renovate",
+  "packageJsonType": "app",
   "packageJsonVersion": "1.0.0",
   "pnpmShrinkwrap": undefined,
   "yarnLock": undefined,
diff --git a/test/manager/npm/extract/type.spec.js b/test/manager/npm/extract/type.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..881eba25571c3286c068b6ffbaf61059b1853367
--- /dev/null
+++ b/test/manager/npm/extract/type.spec.js
@@ -0,0 +1,20 @@
+const {
+  mightBeABrowserLibrary,
+} = require('../../../../lib/manager/npm/extract/type');
+
+describe('manager/npm/extract/type', () => {
+  describe('.mightBeABrowserLibrary()', () => {
+    it('is not a library if private', () => {
+      const isLibrary = mightBeABrowserLibrary({ private: true });
+      expect(isLibrary).toBe(false);
+    });
+    it('is not a library if no main', () => {
+      const isLibrary = mightBeABrowserLibrary({});
+      expect(isLibrary).toBe(false);
+    });
+    it('is a library if has a main', () => {
+      const isLibrary = mightBeABrowserLibrary({ main: 'index.js ' });
+      expect(isLibrary).toBe(true);
+    });
+  });
+});
diff --git a/test/workers/repository/process/__snapshots__/fetch.spec.js.snap b/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
index 063de32ae4632ba88fc10d98350ef3def0a1c9ba..5a267eaa223c7658a0b2df713ebab06637286e69 100644
--- a/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
+++ b/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
@@ -7,6 +7,15 @@ Object {
       "deps": Array [
         Object {
           "depName": "aaa",
+          "depType": "devDependencies",
+          "updates": Array [
+            "a",
+            "b",
+          ],
+        },
+        Object {
+          "depName": "bbb",
+          "depType": "dependencies",
           "updates": Array [
             "a",
             "b",
@@ -14,6 +23,7 @@ Object {
         },
       ],
       "packageFile": "package.json",
+      "packageJsonType": "app",
     },
   ],
 }
diff --git a/test/workers/repository/process/fetch.spec.js b/test/workers/repository/process/fetch.spec.js
index 87796ab8018cf01e4a34e6ffaa55eb0a72fec36e..fed32a4432e861c028809d378212bd323b10bbd2 100644
--- a/test/workers/repository/process/fetch.spec.js
+++ b/test/workers/repository/process/fetch.spec.js
@@ -49,11 +49,16 @@ describe('workers/repository/process/fetch', () => {
       expect(packageFiles.npm[0].deps[2].updates).toHaveLength(0);
     });
     it('fetches updates', async () => {
+      config.pinVersions = null;
       const packageFiles = {
         npm: [
           {
             packageFile: 'package.json',
-            deps: [{ depName: 'aaa' }],
+            packageJsonType: 'app',
+            deps: [
+              { depName: 'aaa', depType: 'devDependencies' },
+              { depName: 'bbb', depType: 'dependencies' },
+            ],
           },
         ],
       };