-
Notifications
You must be signed in to change notification settings - Fork 4
Notes from Experimenting with SSR
Login is properly setting the token
The following need to reliably retrieve the token
- index
- edit
- new
using the universal-cookies-middleware
neatly attaches the token to the header but i can't nest getInitialProps
on the other machine it's working sort of.
- need to pass down token, user, id as needed
- could a HOC actually help with this?
- what about nested
getInitialProps
?
-
Router.push('/', { query: token })
isn't working right now on successful login... - Linking on the client side is showing error before content loads. this seems relatively easy to fix. - Ref: Getting strange 404 before page loads
https://github.com/luisrudge/next.js-auth0 https://github.com/estrada9166/server-authentication-next.js https://github.com/trandainhan/next.js-example-authentication-with-jwt https://github.com/zeit/next.js/issues/2208
TODO:
I should really really curate the smarter changes from the Next.js rebuild. Off the top of my head that's this stuff.
collect CSS from next.js branchgrab fixes for empty title in new post- the state updating clean up in edit
use Bolt instead of Lerna
In an instance of componentWillMount
fetch a validation endpoint. If it doesn't respond with an error, set the state of off
to be true. On the server, with the validation endpoint, use the pre
method. If the pre
method fails, it should error out on its own, and the controller can reply with whatever.
The non-optimal choice is to decode the token on the client, and check the expiration time against the current time.
Either implementation needs to handle the case for when a fetch request errors out. The user needs feedback.
Or better option just handle the error
Working through this with backpack I'm noticing that the modules in redownwrite
aren't accepting babel transform. Wonder if taking it into the redownwrite repository would help. Spectrum does this by parsing them from the root of their monorepo.
Okay that worked.
Need to update client-side config to use new ReactLoadablePlugin({ filename: './dist/react-loadable' })
Map through routes next comes from express req, res, next
const active = routes.find(route => matchPath(req.url, route));
const requestInitialData = active.component.requestInitialData && activeRoute.component.requestInitialData();
Promise.resolve(requestInitialData).then(initialData => {
const context = { initialData };
const markup = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
}).catch(next)
Needs to go in renderToString()
<script>window.__initialData__ = ${serialize(initialData)}</script>
- Use CORS
- Remove Loadable and start without it and iterate to it.
- Use universal-cookie and pass that into the context.
requestInitialData(context: { bearer token })
- work through serialized data to pull from the staticContext
constructor(props) {
super(props)
let repos
if (__isBrowser__) {
repos = window.__INITIAL_DATA__
delete window.__INITIAL_DATA__
} else {
repos = props.staticContext.data
}
}
The other thing that could be done is the extend Component to a "Container" that preloads the static method()
from the component and then renders the component or a HOC that can do something similar
FWIW, Suspense will fix this
Need some mechanism for handing initial state and resolving data-fetching static getInitialProps()
to handle and resolve on the client when routes are transitioned to.
<RouteContainer />
This grabs a route array from a route config.
async function loadInitialProps(routes, pathname, ctx) {
const promises = []
const match = routes.find(route => {
const match = matchPath(pathname, route)
if (match && route.component && route.component.getInitialProps) {
promises.push(
route.component.load
? route.component
.load() // load it as well
.then(() => route.component.getInitialProps({ match, ...ctx }).catch(() => {}))
: route.component.getInitialProps({ match, ...ctx }).catch(() => {})
)
}
return match
})
return {
match,
data: (await Promise.all(promises))[0]
}
}
class RouteContainer extends Component {}
Need to inject default state of auth, which we can check by getting the cookie
. We can decode it, can pass everything into the constructor of the Container in unstated
So we're going to need somethings
- We're going to need custom babel
- We're going to need custom eslint reporting
- We're going to need custom postcss (which we can add more to)
- We're going to need custom jest setup
- We're going to need an HMR express with dev server
- We're going to need to update the service worker precache plugins
- We're going to need to use the Offline Plugin
- We're going to need to use React Loadable
- We're going to need multiple entry points for
server.js
and one forclient.js
- We're going to need to proxy the end points
- We're going to need integration testing (maybe this should be it's own workspace)
Should
- keep all configs in package.json
- try and scaffold out the components in redownwrite into UI package
Eventually:
- We're going to need to handle GraphQL tags in the ES6
But if we go toward the GraphQL end of things we can experiment with another workspace and react scripts
Using an SSR approach would remove the need for working with HTML Plugins for webpack. The renderer template literal will need to account for the #root
, the favicon, the manifest file.
`
<!DOCTYPE html>
<head>
<meta name='theme-color' content='#4FA5C2' />
<title>Downwrite</title>
${tags}
${createLinkTag({ href: `/static/css/${link}` })}
</head>
<body>
<div id="root">
${body}
</div>
${bundles.map(src => createScriptTag({ src }))}
${createScriptTag({ src: `/static/js/${scripts}` })}
</body>
</html>
`
React Loadable has an issue, when we wrap the component in the function we no longer have access to the Component.getInitialData() method that we attempt to resolve on the server before we send the initial tree back. There should be a way around this.
In the config we could specify the path to the component we could do this:
Loadable({
loader: () => import('./Bar'),
modules: ['./Bar'],
webpack: () => [require.resolveWeak('./Bar')],
});
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
render(loaded, props) {
let Bar = loaded.Bar.default;
let i18n = loaded.i18n;
return <Bar {...props} i18n={i18n}/>;
},
});
that might fix the issue.
We can funnel out the Component.getInitialData some other way. that might require a few issues to be resolved first.
Ditch react-loadable for the preset and iterate on solution 1.
Consider using react-loadable for components like the Editor, Export and save async routing for Suspense
NOTE:
Initially it might've been easier to map through only the active route.
routes.filter(route => activeRoute.path === route.path)
export const findRoute = (
routes: Array<RouteObject>,
authed: boolean,
initialData: {}
) =>
routes.map((route, i) => {
const Route = chooseRoute(route)
const Cx = chooseComponent(route, authed)
const SSRCx = props => <Cx {...props} {...initialData} />
return <Route key={i} {...route} component={SSRCx} />
})