Next.js: Programmatic API

Created on 23 Nov 2016  ·  30Comments  ·  Source: vercel/next.js

Moving forward, it will be possible to substitute next start in your programs with your own script, initialized without any wrappers. For example, node index.js.

An example of such a script in its most basic form would be as follows:

const next = require('next')
const { createServer } = require('http')

// init next
const app = next(/* __dirname */) 
const handle = app.getRequestHandler()

// init normal http server
createServer(handle).listen(3000)

This basically does nothing interesting, it's the equivalent of next -p 3000.

An important observation: notice that the parameter passed to next() to initialize the app is not a config object, but a path. This is because the configuration will be conventionally located in a next.config.js file where a next project lives. Credit goes to @Compulves (#222) for this great contribution.

This is crucial because even though we get to substitute the server code, we _don't break_ existing and future useful next sub-commands. Especially next build, which makes deployment to production of your next apps so convenient, continues to work well even with both node server.js and next start.

Custom routing

Next.js's pages/ serves two purposes: to give you a super easy-to-use API for defining routes, but more importantly, to define different entry points for code splitting!

I say more importantly because it should be (and will be) possible to completely override the default route mapping, by invoking next/render manually.

The following example shows how you could point /a to pages/b.js and /b to pages/a.js, effectively changing the default routing behavior

const next = require('next')
const render = require('next/render')
const { createServer } = require('http')
const { parse } = require('url')

// init next
const app = next(/* __dirname */) 
const handle = app.getRequestHandler()

// init normal http server
createServer((req, res) => {
  const { query, pathname } = parse(req.url, true)
  if ('/a' === pathname) {
    render(app, res, 'b', qry)
  } else if ('/b' === pathname) {
    render(app, req, res, 'a', query)
  } else {
    handle(req, res)
  }
}).listen(3000)

In addition to the render method, we'll expose lower level render APIs, like renderToHTML and renderError, which work like render but don't do the automatic response handling. Those APIs are useful for building a caching layer, for example, which is really handy if you know that a certain component will _always_ return the same HTML with given props.

More complicated routes like /posts/:id/tags or even /:anything can be handled by just parsing the URL and passing the appropriate props as the last parameter of render (as seen in the example), by using something like path-match

This is an example of how you do a catch all route, so that every request to /catch/* goes to a page pages/my/page.js:

const next = require('next')
const render = require('next/render')
const { createServer } = require('http')
const { parse } = require('url')

// init next
const app = next(/* __dirname */) 
const handle = app.getRequestHandler()

// set up routes
const match = require('path-match')()
const route = match('/catch/:all')

// init normal http server
createServer((req, res) => {
  const { query, pathname }  = parse(req.url, true)
  const params = match(pathname);
  if (false === params) {
    // handle it with default next behavior
    handle(req, res)
  } else {
    // renders `pages/my/page.js`
    render(app, req, res, 'my/page', query)
  }
}).listen(3000)

The idea here is that we retain the smallest possible API surface, minimize the number of dependencies you have to install and code to evaluate.

Fancy routing approaches vary widely, and we don't want to lock you in to a particular one. It should be entirely possible to implement express instead of http.createServer, for example.

p0

Most helpful comment

The priority tag is perfect.
Thank you.

All 30 comments

Important: since we make requests to get a JSON representation of the components for lazy loading, we'll be switching those to Accept-Encoding instead of a .json suffix. That should make server code easier to write, as well.

The priority tag is perfect.
Thank you.

How does this work client-side? When you click a link, do you have to go back to the server to see what the following block evaluates to?

createServer((req, res) => {
  const { query, pathname } = parse(req.url, true)
  if ('/a' === pathname) {
    render(app, res, 'b', qry)
  } else if ('/b' === pathname) {
    render(app, req, res, 'a', query)
  } else {
    handle(req, res)
  }
})

@pemrouz the client makes the href explicit, and can decorate optionally the URL with as.

<Link href="/user?id=3" as="/pemrouz">

this is so that we can cache and pre-fetch components more effectively :)

Very excited to see this take shape! One question about the proposed render method:

// renders `pages/my/page.js`
render(app, req, res, 'my/page', query)

I'm wondering if this could be done without passing req and res to render.

Seems like separating the concerns of rendering and handling the request would be useful — boiling it down to a render method that only provides the output of the render, leaving the HTTP response up to the caller.

Something more like this:

render(app, 'my/page', query)
  .then(content => {
    res.send(content)
  })

@wookiehangover that's the case: https://github.com/zeit/next.js/pull/310

@rauchg so my question is more about removing the dependency on the res
and req objects, which #310 still appears to have — but thanks for
letting me know that there's something concrete to look at, hadn't noticed
that ☺️
On Thu, Dec 1, 2016 at 9:43 PM Guillermo Rauch notifications@github.com
wrote:

@wookiehangover https://github.com/wookiehangover that's the case: #310
https://github.com/zeit/next.js/pull/310


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/zeit/next.js/issues/291#issuecomment-264378362, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAFKpE7P9E9n9jZERgomJuI4Y3Luv-Oiks5rD6_-gaJpZM4K647K
.

The dependence on those objects is that getInitialProps requires them for distinguishing between server and client for example

will the as prop work in both the client and the server automagically?

@luisrudge as long as the underlying <a> is output with the as value in href, everything should work out of the box. For example, command+click (open in new tab), would go the as.

But that goes for both server and client. The server doesn't really "care" about as other than that detail I described

I think that could work out well. I was initially unsure, if this implies having the url mappings duplicated across every link. But actually this can be solved through composition: E.g. with a global configuration that is put on context and a custom Link wrapper like:

{
  users: {
    url: '/users',
    page: '/users'
  },
  userDetails: {
    url: '/users/{userId}',
    page: '/userDetails'
  }
}

<CustomLink to="userDetails" params={{userId: '123'}}>User</CustomLink>

could render:

<Link href="/users/123" as="/users">User</Link>

Something like this should be possible, right?

indeed.

Is a PR wanted for this or is this more internal team stuff since it's more core?

Is it safe to assume once this get's released, most will default to express? Maybe we should gear the examples for the case? I foresee many issues asking how to get it working with express.

Also wondering if a PR was needed.

@randallb @DarrylD There is already a PR open for this bug: #310
And there is one for an express example: #352

@DarrylD FWIW I'm thinking of using it with WP API. I'm a newb so excuse me if this is stupid/wrong/irrelevant.

Will it be possible to not use the filesystem-based router at all, and still have code splitting for each route?
For example, if I map /post/:id to /components/post.js will webpack still serve only the necessary code?

It would be cool to do some sort of built in index page. For instance /blog/coding contains many posts, if I make an index.js, automatically returning a list of every page in this directory and paginate it would be useful. I imagine many people would use this.

@nmaro that's exactly the case. All that changes is the "look" of the URL. Think of the pages as webpack entry points.
@zackify we won't do anything magical like that, everything will be explicit. You should be able to pull that off with base next

Sounds good. Thanks for the hard work!

On Fri, Dec 16, 2016 at 3:57 PM Guillermo Rauch notifications@github.com
wrote:

@nmaro https://github.com/nmaro that's exactly the case. All that
changes is the "look" of the URL. Think of the pages as webpack entry
points.
@zackify https://github.com/zackify we won't do anything magical like
that, everything will be explicit. You should be able to pull that off with
base next


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/zeit/next.js/issues/291#issuecomment-267694129, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAbacIVXExhswC0e3-AG3QmJrqLX5Eg0ks5rIvszgaJpZM4K647K
.

Sample project making use of Programmatic API would be nice. When I try to require('next') directly from index.js it fails. Looks like there is some build process that needs to take place first? Please clarify how this works.

Yeah so

npm init -y
yarn add next
yarn add random
node
> require('next')
Error: Cannot find module 'next'
> require('random')
{ generateIntegers: [Function],
  generateSequence: [Function],
  generateStrings: [Function],
  checkQuota: [Function] }

So how does the home page example for "custom routing" work? How do you directly require next? Do I ned to configure babel / webpack or something? Seems like custom routing should still have nice things out of the box.

Please clarify.

@babakness If I see it correctly the latest changes that include the programmatic api haven't been published to npm yet

@babakness @webyak install the 2.0 beta: npm i -S next@beta

@webyak @sedubois Ok, thanks. So now it loads but <style jsx> tags don't seem to get applied at all. Thoughts?

I feel confused on this line
const app = next(/* __dirname */)
__dirname for what?

@rohmanhm I'd consider that a "sketch". The final API is documented here https://github.com/zeit/next.js#custom-server-and-routing

@possibilities
Look this image from README Documentation
image

I'm confused about this line
next(path: string, opts: object) - path is

path is not defined

Looks like a typo, if you go back it time it used to say:

  • next(path: string, opts: objecvt) - path is where the Next project is located

https://github.com/zeit/next.js/blame/b337433d14435ef2fc4d193af5c2e7c9ec552583/README.md#L311

It's still not very clear how to add custom middleware like API proxy and not just render components based on the path.

server.use('/api', myCustomMiddleware);

thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  ·  3Comments

pie6k picture pie6k  ·  3Comments

formula349 picture formula349  ·  3Comments

kenji4569 picture kenji4569  ·  3Comments

havefive picture havefive  ·  3Comments