-
Notifications
You must be signed in to change notification settings - Fork 27.8k
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 #310
Programmatic API #310
Conversation
createServer((req, res) => { | ||
const accept = accepts(req) | ||
const type = accept.type(['json', 'html']) | ||
if (type === 'json') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't read the code in full, why do we need to do this?
If that's related to our .json
file serving, can't app.render()
could check the accept type
and do something about it?
The idea is user has nothing to worry about our .json file serving?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually for .json
file serving which is changed to use the Accept
header with this PR.
The main reason we don't serve it using app.render
is JSON endpoint for a component should be only one, so that you can use <Link href="/foo" as="/bar">
easier, for example.
Another reason is we have app.renderToHTML
method too which you'd have to check the Accept
header for using it anyway.
But I think it's arguable. Let me know if you have any ideas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, my idea to remove this overhead from the user.
Anyway do we need to do this:
<Link href="/foo" as="/bar">
Since we are defining routes on the server. I think client doesn't need to know about anything about the actual mapping. So could do this:
<Link href="/blog/postId">
If we can somehow do this, this will be awesome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<Link href="/foo" as="/bar">
is for caching. A JSON response for a component is always identical, so we want to allow only <Link href="/blog" as="/blog/123">
instead of <Link href="/blog/123">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. That makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like as
for it implies decoration. We could also call it display
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also feel this is a bit confusing since href
is an existing prop which we give a different meaning.
export default () => ( | ||
<ul> | ||
<li><Link href='/b' as='/a'><a>a</a></Link></li> | ||
<li><Link href='/a' as='/b'><a>b</a></Link></li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should replace as
with pathname
, route
or something like that, since you'd always like to write actual url to href
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add some comments here about Link
: #310 (comment)
I am not sure how to do this, but if we could find to use just <Link href='xxx'/>
on the client that would be awesome.
app.prepare() | ||
.then(() => { | ||
createServer((req, res) => { | ||
const accept = accepts(req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means parsing the Accept
header can happen twice, since we do it in our handler too :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nkzawa can't handle
have an optional 3rd argument for parsing accept header, so that we just going to have to parse it once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good. Alternatively, we can have two handlers, one is for JSON, the other is for HTML.
import HotReloader from './hot-reloader' | ||
import { resolveFromList } from './resolve' | ||
|
||
export default class Server { | ||
constructor ({ dir = '.', dev = false, hotReload = false }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hotReload
option is removed since only dev: true, hotReload: true
or dev: false, hotReload: false
work for now anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this.
I don't think there's a much of a use case for dev: false
and hotReload: true
We may need to enable hotReload in production for mobile apps, but we could do it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @arunoda. Very cool simplification
const app = next('.', { dev: true }) | ||
const handle = app.getRequestHandler() | ||
|
||
app.prepare() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to wait app
ready.
import Server from './' | ||
|
||
module.exports = (dir, opts) => { | ||
return new Server(dir, opts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can make dir
optional and default to .
(current directory).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nkzawa please
app.render(req, res, '/b', query) | ||
} else if (pathname === '/b') { | ||
app.render(req, res, '/a', query) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to this basic switching, I think the example should also explicitly show how to make a parameterized route (e.g. /blog/:id
). IMHO, this will mitigate tons of questions/confusion.
This is awesome btw!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 @jaredpalmer. We should have a basic example and a route pattern matching example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rauchg maybe i'm confused, but how do you get the params to the react page after matching on the server? this.props.url.params
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My temporary workaround is this:
let { pathname, query } = parse(req.url, true)
if (route('/blog/:id')(pathname).id) {
query = query || {}
query.params = route('/blog/:id')(pathname)
app.render(req, res, '/blog__id', query)
} else {
handle(req, res)
}
I feel like app.render
should accept an optional params
argument that gets passed into getInitialProps(ctx)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that was our plan @nkzawa?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that's the recommended way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the example of parameterized routing was added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rauchg @nkzawa The Link
implementation prevents access to the "desired" window object on the client of the decorated route during getInitialProps
. You can test this by logging window.location
in and out of getInitialProps
and following a Link
. The window.location
will represent that of the href
and not as
, so things like are not accessible for data fetching. In contrast, the back button will give you the "correct" window.location
during getInitialProps
because it is a full render. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jaredpalmer you can use pathname
and query
arguments of getInitialProps
instead of window.location
.
return | ||
} | ||
|
||
const { pathname, query } = parse(req.url, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parsing url also can happen multiple times :(
Maybe handle
should take pathname
and query
as optional arguments ?
handle(req, res, pathname, query)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for this since its opt-in it wouldn't affect simplicity of the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nkzawa or pass the un-destructured url object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handle(req, res, pathname, query)
seems better since it's same arguments with app.render()
.
But on handle()
, we don't want to allow customizing pathname
and query
like app.render()
, so un-destructured url object also makes sense, umm.
#370 mentioned being able to use a database via a programmatic API. How would that work with this PR as it stands? My thought would have been some sort of way to pass data into the root react component or to define |
* Add custom-server-express example * Remove extraneous nexts in express routes defs * Update next config in server.js
I'd ❤️ to see this land soon. |
* Handle accept headers totally inside Next.js Now user doesn't need to handle it anymore. * Move json pages serving to /_next/pages base path. * Join paths correctly.
@@ -2,7 +2,7 @@ | |||
"name": "next", | |||
"version": "1.2.3", | |||
"description": "Minimalistic framework for server-rendered React applications", | |||
"main": "./dist/lib/index.js", | |||
"main": "./dist/server/next.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be incorrect. Looks like it should be ./dist/server/index.js
$ ls node_modules/next/dist/server
build config.js hot-reloader.js index.js read.js render.js require.js resolve.js router.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
never mind I see you haven't published a new version
const next = require('next') | ||
const pathMatch = require('path-match') | ||
|
||
const app = next({ dev: true }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on https://github.com/zeit/next.js/pull/310/files#r92954990, looking at ./dist/server/index.js
the api seems to require new next({...})
to create the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh never mind I see you haven't published a new version
Failing test suitestest/integration/app-document/test/index.test.js
Expand output● Document and App › Client side › should detect the changes to pages/_document.js and display it
|
Fixes #291
Ideas are basically the same with #291, but some APIs are a bit different.
API
next({ dir = '.', dev = false } = {})
app.render(req, res, pathname, query)
- renderspathname
and automatically handle response. renders/_error
and set status code tores.statusCode
if an error occurred.app.renderToHTML(req, res, pathname, query)
- similar toapp.render
but doesn't automatically handle response and returns a Promise of html string.app.renderError(err, req, res, pathname, query)
- similar toapp.render
but renders/_error
(or the error page for debug ifdev
istrue
).app.renderErrorToHTML(err, req, res, pathname, query)
- similar toapp.renderError
but doesn't automatically handle response and returns a Promise of html string.app.prepare()
- wait to be prepared for serving.Link
It takes a new prop
as
which is url value andhref
represents the path for the page component ifas
exists.For example, it fetches the
/foo
component and url transitions to/bar
if you click the following link.