New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programmatic API #291

Closed
rauchg opened this Issue Nov 23, 2016 · 30 comments

Comments

@rauchg
Contributor

rauchg commented Nov 23, 2016

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.

@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Nov 23, 2016

Contributor

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.

Contributor

rauchg commented Nov 23, 2016

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.

@Francisc

This comment has been minimized.

Show comment
Hide comment
@Francisc

Francisc Nov 24, 2016

The priority tag is perfect.
Thank you.

The priority tag is perfect.
Thank you.

@pemrouz

This comment has been minimized.

Show comment
Hide comment
@pemrouz

pemrouz Nov 28, 2016

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 commented Nov 28, 2016

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)
  }
})
@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 1, 2016

Contributor

@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 :)

Contributor

rauchg commented Dec 1, 2016

@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 :)

@wookiehangover

This comment has been minimized.

Show comment
Hide comment
@wookiehangover

wookiehangover Dec 2, 2016

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 commented Dec 2, 2016

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)
  })
@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 2, 2016

Contributor

@wookiehangover that's the case: #310

Contributor

rauchg commented Dec 2, 2016

@wookiehangover that's the case: #310

@wookiehangover

This comment has been minimized.

Show comment
Hide comment
@wookiehangover

wookiehangover Dec 2, 2016

@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 2, 2016

Contributor

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

Contributor

rauchg commented Dec 2, 2016

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

@luisrudge

This comment has been minimized.

Show comment
Hide comment
@luisrudge

luisrudge Dec 4, 2016

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

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

@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 5, 2016

Contributor

@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

Contributor

rauchg commented Dec 5, 2016

@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

@amannn

This comment has been minimized.

Show comment
Hide comment
@amannn

amannn Dec 5, 2016

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?

amannn commented Dec 5, 2016

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?

@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 8, 2016

Contributor

indeed.

Contributor

rauchg commented Dec 8, 2016

indeed.

@randallb

This comment has been minimized.

Show comment
Hide comment
@randallb

randallb Dec 8, 2016

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

randallb commented Dec 8, 2016

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

@DarrylD

This comment has been minimized.

Show comment
Hide comment
@DarrylD

DarrylD Dec 8, 2016

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.

DarrylD commented Dec 8, 2016

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.

@chr1shaefn3r

This comment has been minimized.

Show comment
Hide comment
@chr1shaefn3r

chr1shaefn3r Dec 8, 2016

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

chr1shaefn3r commented Dec 8, 2016

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

@corysimmons

This comment has been minimized.

Show comment
Hide comment
@corysimmons

corysimmons Dec 12, 2016

Contributor

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

Contributor

corysimmons commented Dec 12, 2016

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

@nmaro

This comment has been minimized.

Show comment
Hide comment
@nmaro

nmaro Dec 12, 2016

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?

nmaro commented Dec 12, 2016

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?

@zackify

This comment has been minimized.

Show comment
Hide comment
@zackify

zackify Dec 16, 2016

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.

zackify commented Dec 16, 2016

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.

@rauchg rauchg closed this in #310 Dec 16, 2016

@rauchg

This comment has been minimized.

Show comment
Hide comment
@rauchg

rauchg Dec 16, 2016

Contributor

@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

Contributor

rauchg commented Dec 16, 2016

@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

@zackify

This comment has been minimized.

Show comment
Hide comment
@zackify

zackify Dec 16, 2016

zackify commented Dec 16, 2016

@babakness

This comment has been minimized.

Show comment
Hide comment
@babakness

babakness Dec 22, 2016

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.

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.

@babakness

This comment has been minimized.

Show comment
Hide comment
@babakness

babakness Dec 22, 2016

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.

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.

@webyak

This comment has been minimized.

Show comment
Hide comment
@webyak

webyak Dec 22, 2016

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

webyak commented Dec 22, 2016

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

@sedubois

This comment has been minimized.

Show comment
Hide comment
@sedubois

sedubois Dec 22, 2016

Contributor

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

Contributor

sedubois commented Dec 22, 2016

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

@babakness

This comment has been minimized.

Show comment
Hide comment
@babakness

babakness Dec 22, 2016

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

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

@rohmanhm

This comment has been minimized.

Show comment
Hide comment
@rohmanhm

rohmanhm Mar 28, 2017

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

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

@possibilities

This comment has been minimized.

Show comment
Hide comment
@possibilities

possibilities Mar 28, 2017

Contributor

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

Contributor

possibilities commented Mar 28, 2017

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

@rohmanhm

This comment has been minimized.

Show comment
Hide comment
@rohmanhm

rohmanhm Mar 28, 2017

@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

rohmanhm commented Mar 28, 2017

@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

@possibilities

This comment has been minimized.

Show comment
Hide comment
@possibilities

possibilities Mar 28, 2017

Contributor

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

Contributor

possibilities commented Mar 28, 2017

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

@abachuk

This comment has been minimized.

Show comment
Hide comment
@abachuk

abachuk May 27, 2017

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.

abachuk commented May 27, 2017

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.

@lock lock bot locked as resolved and limited conversation to collaborators May 11, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.