Next.js: How to transpile server files

Created on 15 Apr 2017  路  24Comments  路  Source: vercel/next.js

Hello,

I would like to use import syntax in my custom server&routing is it possible? I am requiring my module which is using imports and I am getting this error.

(function (exports, require, module, __filename, __dirname) { import Router from 'koa-router'
                                                              ^^^^^^
SyntaxError: Unexpected token import

I have tried to edit my .babelrc but with no success.

{
  "plugins": [
    ["module-resolver", {
      "root": ["./"]
    }]
  ],
  "presets": [
    "next/babel",
    "es2015",
    "stage-0"
  ]
}

Most helpful comment

I'd also be curious to know if there is any "officially recommended" way to achieve this, without terrible performance, inconsistencies across your project, and lots of babel deps.

I think enough NextJs users want this, that there should be a relatively easy way to get this done without everyone rolling their own.

All 24 comments

You must implement it yourself, either with just babel or with a custom webpack configuration. Next doesn't transpile your custom server.

@sergiodxa Is there any documentation or resources on how this can be done?

I have also updated my .babelrc config

{
  "plugins": [
    [
      "module-resolver",
      {
        "root": ["."],
        "alias": {
          "styles": "./styles"
        },
        "cwd": "babelrc"
      }
    ],
    [
      "wrap-in-js",
      {
        "extensions": ["css$"]
      }
    ]
  ],
  "presets": [
    "next/babel",
    "es2015",
    "stage-0",
  ],
  "ignore": []
}

I'm assuming I need to tell babel what file to 'watch' and transpile?

Yeah once you have a custom .babelrc file you need to run babel-cli against your custom server files. But I wouldn't recommend you to do that, with Node.js v7 you already have a lot of ES2015 and greater features, you can use require instead of Babel and avoid the transpile.

Your universal code should be handled by Next.js in your pages and imported modules. Your custom server should be Node.js compatible and only have custom routing and other few things (passport for auth maybe).

@sergiodxa When using TypeScript, the development experience is pretty awful. You have to use import statements to get the typings, and then repeat them to get the actual imports. For example:

import HTTP from 'http'
import URL from 'url'
import Next from 'next'

const { createServer } = require('http') as typeof HTTP
const { parse } = require('url') as typeof URL
const next = require('next') as typeof Next

Trying to get server-side Next code to be similar to the client code is practically impossible, as far as I can tell. I've tried compiling my src/ directory to lib/ with tsc, then using babel to compile lib to out, and I just can't get it working. I have to include es2015 and stage-0 babel plugins for the server imports to work, but this seems to cause issues with Next.

If I have typescript compile es6 modules to commonjs rather than letting babel/webpack handle it, then my server code can use es6 imports but I can no longer use dynamic imports in my client (because of the special SameLoopPromise class that Next uses).

So the conclusion seems to be "only use next for serving static assets, and create a completely different project with a completely different framework for your API". That seems like an unfortunate place for Next to put itself into. I want a server framework that can do SSR and other server responsibilities. Suddenly I'm struggling to see the value

@sbking Given the trouble I decided to just not use es6 import syntax in my custom server. Doesn't seem worth it.

@gragland That's pretty much what I've decided, as well, but having to double up all of my import statements to get typings is a poor compromise, in my opinion. It also means that the only universal code in my application are the things handled directly by Next.

What's the solution for writing a validation library that needs to run on both client and server? Which module style should you use?

The answer, as far as I can tell, seems to be to not use the validation library in the actual Next custom server, but instead to create a separate node application just for the API, and then use git submodules or NPM private modules or something to share the validation library. Suddenly there are three separate projects to manage. This makes me question the utility of Next.js for anything but the most basic applications.

@sergiodxa @gragland Okay, a little more tweaking and I think I've solved my issues, allowing me to have my server-only code in src/api and my next universal code in src/ui, and still letting me use ES6 imports anywhere except next.config.ts, and allowing dynamic import() calls to still work in the UI code. I believe the problem was that I needed to put the next.config.ts file in my UI project subdirectory (where my next pages dir lives), rather than in my repository root. Here are the settings that ended up working for me:

package.json:

  "scripts": {
    "dev": "concurrently 'npm run compile-watch' 'npm run test-watch' 'npm run start-watch'",
    "lint-api": "tslint --type-check --project src/api",
    "lint-ui": "tslint --type-check --project src/ui",
    "lint": "npm run lint-api && npm run lint-ui",
    "compile-api": "tsc --project src/api",
    "compile-api-watch": "tsc --project src/api --watch",
    "compile-ui": "tsc --project src/ui",
    "compile-ui-watch": "tsc --project src/ui --watch",
    "compile-watch": "npm run compile-api-watch && npm run compile-ui-watch",
    "compile-only": "npm run compile-api && npm run compile-ui",
    "compile": "npm run lint && npm run compile-only",
    "test-watch": "jest --coverage --verbose --watchAll",
    "test-only": "jest --coverage --verbose",
    "test": "npm run compile && npm run test-only",
    "build-only": "next build out/ui",
    "build": "npm run test && npm run build-only",
    "start-watch": "nodemon -w out/api -w yarn.lock out/api/server.js",
    "start": "NODE_ENV=production node out/api/server.js"
  },

.babelrc:

{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    ["module-resolver", {
      "root": ["./out"],
      "cwd": "babelrc"
    }]
  ]
}

tsconfig.base.json:

{
  "compilerOptions": {
    "moduleResolution": "node",
    "target": "esnext",
    "lib": ["esnext", "dom"],
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "experimentalDecorators": true
  }
}

src/api/tsconfig.json:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "module": "commonjs",
    "baseUrl": "..",
    "outDir": "../../out/api"
  },
  "include": [
    ".",
    "../types"
  ]
}

src/ui/tsconfig.json:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "module": "esnext",
    "baseUrl": "..",
    "outDir": "../../out/ui",
    "jsx": "react-native"
  },
  "include": [
    ".",
    "../types"
  ]
}

src/ui/next.config.ts:

import { Config } from 'next/config'

const config: Config = {
  distDir: '../../build',
  webpack: (config) => {
    if (config.resolve && config.resolve.alias) {
      delete config.resolve.alias['react']
      delete config.resolve.alias['react-dom']
    }
    return config
  }
}

module.exports = config

Usually I transpile server code like this.
Imagine we have server.js with fancy "ES-whatever" code, then you create ie index.js with something like:

require('babel-register')({
    babelrc: false, // Tell babel-register to ignore the .babelrc file
    presets: {
        'env': {
            'development': {'presets': ['react', 'stage-1', 'env', 'es2015']},
            'production': {'presets': ['react', 'stage-1', 'env', 'es2015']},
            'test': {'presets': [['react', 'stage-1', 'env', 'es2015', {'modules': 'commonjs'}]]}
        }
    },
    plugins: [['transform-runtime', {'polyfill': false, 'regenerator': true}]]
});
require('./server');

And in package.json you specify your application start like:

"start": "NODE_ENV=production node ./server/index.js"

and that's it, you normally write all ES* features on server side

I managed to get mine working with this configuration.

// ./devServer.js
require('babel-register')({
  babelrc: false,
  presets: [
    [
      'env',
      {
        targets: {
          node: '8',
        },
      },
    ],
    'stage-3', // I use object-reset-spread 馃榾
  ],
})
require('./server')

My server "source" files are contained in a "server" folder within an "index.js" being the entry point. I transpile the server source into a ".server" folder using the build step in prep for production execution. Best to add the ".server" folder to your gitignore list.

And then in my package.json

json{ "scripts": { "dev": "NODE_ENV=development nodemon -w server devServer.js", "build:next": "NODE_ENV=production next build ./frontend", "build:server": "NODE_ENV=production babel -s -D -d .server server", "build": "npm run build:next && npm run build:server", "start": "NODE_ENV=production node .server" }

Another solution with next, express, babel and nodemon.

server.js

import express from 'express'
import next from 'next'

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app
  .prepare()
  .then(() => {
    const server = express()

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })

package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server.js --exec babel-node --presets babel-preset-env --watch server.js",
    "build": "yarn build:next && yarn build:server",
    "build:next": "next build",
    "build:server": "cross-env NODE_ENV=production babel server.js --out-dir .next/dist",
    "start": "cross-env NODE_ENV=production node .next/dist/server.js"
  },
  "dependencies": {
    "cross-env": "^5.1.3",
    "express": "^4.16.2",
    "next": "^4.2.3",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.6.1",
    "nodemon": "^1.14.11"
  },
  "babel": {
    "env": {
      "development": {
        "presets": [
          "next/babel"
        ]
      },
      "production": {
        "presets": [
          "next/babel",
          [
            "env",
            {
              "targets": {
                "node": "8"
              }
            }
          ]
        ]
      },
      "test": {
        "presets": [
          [
            "next/babel",
            {
              "preset-env": {
                "modules": "commonjs"
              }
            }
          ]
        ]
      }
    }
  }
}

@sergiodxa / @timneutkens Should I create an example?

@ctrlplusb work for me

@danielbayerlein could you create an example?

I prefer @danielbayerlein's approach over the one I posted.

Inspired by @danielbayerlein and in case someone likes to have a running version with next 6.0.3 the package.json should now use the following dependencies:

{
  "scripts": {
    "dev": "NODE_ENV=development nodemon ./server.js --exec babel-node --presets @babel/preset-env --watch server.js",
    "build": "yarn build:next && yarn build:server",
    "build:next": "next build",
    "build:server": "NODE_ENV=production babel server.js --out-dir .next/dist",
    "start": "NODE_ENV=production node .next/dist/server.js"
  },
  "babel": {
    "env": {
      "development": {
        "presets": [
          "next/babel"
        ]
      },
      "production": {
        "presets": [
          "next/babel",
          [
            "@babel/env",
            {
              "targets": {
                "node": "8"
              }
            }
          ]
        ]
      }
    }
  },
  "dependencies": {
    "next": "^6.0.3",
    "react": "^16.4.1",
    "react-dom": "^16.4.1"
  },
  "devDependencies": {
    "@babel/cli": "^7.0.0-beta.44",
    "@babel/core": "^7.0.0-beta.44",
    "@babel/node": "^7.0.0-beta.44",
    "@babel/preset-env": "^7.0.0-beta.44",
    "nodemon": "^1.17.5"
  }
}

Tip: Next.js uses a plain require('build/...') to require a page on the server-side. If you use a custom server with @babel/register, you need to explicitly ignore the build dir, and also pages (because it will be transpiled by webpack.)

This prevents @babel/register trying to parse a usually very large already concatenated and transpiled code. Improves startup time.

Also, @babel/register doesn't save its cache properly on exit. You should manually call require('@babel/register/lib/cache.js').save() when exiting your app. Also, process.env.BABEL_CACHE_PATH is what you use to set the cache dir. It defaults to node_modules/.cache. Also improves startup time.

@vjpr Could you provide a code sample which handles ignoring those directories and saving the cache on exit?

Also, is @babel/register something I'll opt into using if I use Next's setup? I feel like we want our server and client transpilation to be identical.

I'd also be curious to know if there is any "officially recommended" way to achieve this, without terrible performance, inconsistencies across your project, and lots of babel deps.

I think enough NextJs users want this, that there should be a relatively easy way to get this done without everyone rolling their own.

The main usecase is re-usable code for server/client (think util files...). But you cannot write all ES6 or import/export in pure Node.js.

I really think transpiling (not necessarily bundling) server code is a must-have. Especially on medium/large projects

For what it's worth, I've opted to go for native module support in Node 10, via the experimental-modules flag. Most likely this is only going to be an option for people starting new projects, or those willing to upgrade to the latest version of Node.

Yes, I'm aware there could be breaking changes in future Node versions, but the same is true of Babel unless you hold onto "legacy" settings. The extension issue doesn't really apply here, as we'll only need to change our server file, which is never handled by browsers.

Here's what a sample package.json might look like:

"name": "nextjs-node-ten",
"engineStrict": true,
"engines": {
  "node": ">=10.11.0"
},
"scripts": {
  "dev": "node --experimental-modules server.mjs",
  "build": "next build",
  "start": "NODE_ENV=production node --experimental-modules server.mjs"
}

Notice that you do have to change your server.js extension to mjs. There's no way around this, but you can then import regular .js files from within it.

With this, you get native Node performance without a hit on startup time, no transpilation or memory issues of running babel-node in production, no build step for your server code, etc.

There are a few small gotchas, like __dirname being undefined, and so on. I was able to find quick solutions and have my server code running natively in Node in about 5 minutes.

Hopefully within a year we won't be discussing module support anymore...

I have a start.js file which wraps server.js:

require("@babel/register")({
  presets: ["@babel/preset-env"],
  ignore: ["node_modules", ".next"]
});

// Import the rest of our application.
module.exports = require("./server.js");

Ensure you aren't transpiling the .next directory, else your compile times will spiral out of control.

there is a great package, esm, which you just install and then change the nodemon server.js with

nodemon -r esm server.js

that's it!

I have a start.js file which wraps server.js:

require("@babel/register")({
  presets: ["@babel/preset-env"],
  ignore: ["node_modules", ".next"]
});

// Import the rest of our application.
module.exports = require("./server.js");

Ensure you aren't transpiling the .next directory, else your compile times will spiral out of control.

It's worked for me.

there is a great package, esm, which you just install and then change the nodemon server.js with

nodemon -r esm server.js

that's it!

Any pointers on how you configured esm for nextjs?

I have a start.js file which wraps server.js:

require("@babel/register")({
  presets: ["@babel/preset-env"],
  ignore: ["node_modules", ".next"]
});

// Import the rest of our application.
module.exports = require("./server.js");

Ensure you aren't transpiling the .next directory, else your compile times will spiral out of control.

Excelente. Me ha servido mucho. Le agregu茅 otro par谩metro m谩s

require('@babel/register')({
  cache: false,
  babelrc: false,
  presets: ['@babel/preset-env'],
  plugins: [['inline-dotenv'], ['console-source', { segments: 2 }]],
  ignore: ['node_modules', '.next']
})

// Import the rest of our application.
module.exports = require('./server.js')

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pie6k picture pie6k  路  3Comments

olifante picture olifante  路  3Comments

timneutkens picture timneutkens  路  3Comments

swrdfish picture swrdfish  路  3Comments

renatorib picture renatorib  路  3Comments