Skip to content
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

SPA Mode for Smart TV Apps #9587

Open
grushetsky opened this issue Dec 2, 2019 · 10 comments

Comments

@grushetsky
Copy link
Contributor

@grushetsky grushetsky commented Dec 2, 2019

Feature Request

The Problem

Next.js can't be used for apps running on Smart TV (e.g., on Samsung Tizen platform) or for other client-side rendered apps with dynamic data requirements.

It would be great if Next.js could be used for client-side only apps and still support dynamic routing and automatic code-splitting.

The Solution

SPA Mode

Next.js introduces a mode for building app as a single-page application (SPA). getInitialProps works just like before, but is executed on the client only.

The new option for SPA mode is added to next.config.js:

module.exports = {
  …,
  spa: true
};

Using SPA and SSG

The open question is how to handle both SPA mode and SSG. It looks like it's still possible to do it on a per-page basis (suggested in this RFC). Although there's no clear benefit of using a hybrid app for Smart TV use case as TV apps are hosted locally on the device.

Considered Alternatives

Static export is not an option because the app has dynamic data requirements.

SSR doesn't make sense because the app is hosted locally on the TV device. It's not a technical option either because Node.js isn't available on the TV.

So, the alternative that is left is running the app without Next.js.

Additional context

This RFC mentions the support of "client-side only applications" as one of the goals.

Nuxt.js supports a dedicated mode for building SPA.

Smart TV Apps 101

Smart TV apps are web apps that are executed by TV's browser engine:

  1. The developer creates an application bundle: styles, scripts, images, index.html, certificates, TV config file, etc.
  2. The bundle is submitted for review to TV's app store.
  3. Upon successful review, the app is added to the app store and is available for download.
  4. The user downloads the app.
  5. The app is hosted by the TV and is run by the user.

Some Smart TV platforms like webOS TV support hosted web apps, others like Tizen prohibit such kind of apps.

@kachkaev

This comment has been minimized.

Copy link
Contributor

@kachkaev kachkaev commented Dec 2, 2019

👋 @grushetsky,

I might be wrong, but I'm not sure why using Next.js in 'SPA mode' is a problem now. You can achieve this by limiting your app to just one page:

// pages/index.js

import dynamic from "next/dynamic";

const SpaRoot = dynamic(
  () => import("../components/SpaRoot"),
  { ssr: false },
);

const IndexPage = () => {
  return <SpaRoot />;
};

export default IndexPage;

And then use next build && next export to produce the production app.

components/SpaRoot can contain any dynamic logic you want as it is only going to load on the client. Eg. you can use your own router or data fetching there. Next.js will play a role of the devserver and will produce you a static bundle too. Things will work similarly to CRA, but with more freedom to customise things if needed. We use Next.js in such 'mode' in a couple of apps that were previously pure webpack-based SPAs.

@grushetsky

This comment has been minimized.

Copy link
Contributor Author

@grushetsky grushetsky commented Dec 2, 2019

@kachkaev, hey! 👋

Thank you for the suggestion! Let me try to dissect this idea.

First, there's a limit of a single page. It might sound reasonable that a single page is enough for a single-page application, but that's not exactly the case. 🙂 With file-system routing, each page is mapped to the corresponding route. This behaviour is enabled by default along with the built-in router, Link component, prefetching, etc. It's undesirable to lose all these features. In the context of single-page application "Next.js page" is probably a confusing name, but conceptually it is as beneficial for SPA as it is for SSG or SSR case.

Second, what about automatic code-splitting? It's a sane thing to do even for an app hosted locally on the TV (to reduce parse time, for example). One can implement code-splitting manually, but then again a handy built-in feature of Next.js is being lost.

Third, if there's an argument in favour of SPA mode it seems that switching between SSR and client-side only mode should be as flawless as possible for current Next.js users. Thus, using the existing app structure and getInitialProps for data population will help to ease the switching.

@kachkaev

This comment has been minimized.

Copy link
Contributor

@kachkaev kachkaev commented Dec 2, 2019

I'm not sure how one could leverage fs-based routing with getInitialProps and other lifecycle page methods. Not saying this is impossible, but feels quite hard to me to figure out what's allowed and what's not allowed in this mode.

As of code splitting, you can do that even if when your app has only one Next.js page. See https://github.com/zeit/next.js#dynamic-import and https://github.com/zeit/next.js/tree/canary/examples/with-dynamic-import

@grushetsky

This comment has been minimized.

Copy link
Contributor Author

@grushetsky grushetsky commented Dec 2, 2019

I'm not sure how one could leverage fs-based routing with getInitialProps and other lifecycle page methods. Not saying this is impossible, but feels quite hard to me to figure out what's allowed and what's not allowed in this mode.

It is a concern. That's why the suggestion is to match those features to their existing semantics and defaults as much as possible so that developers wouldn't need to adapt a lot:

  • File-system routing is turned on by default.
  • Link is used for transitioning between routes (including dynamic routes).
  • The router can be consumed from next/router (as an object, with useRouter or via withRouter HOC).
  • getInitialProps receives the same context object with the same properties as it already does on the client when navigating routes using Link.
  • Custom error page can be introduced using "pages/_error.js".

This list goes on and on, there are many other aspects to consider. So, in order to make it easier to introduce client-side only rendering it is suggested to keep the parity for all current features. And for cases that can't be handled on the client (or just don't make sense) introduce API changes or special notes. The assumption is that there's not going to be lots of these special cases.

Please, let me know if any major roadblock isn't taken into account.

As of code splitting, you can do that even if when your app has only one Next.js page. See https://github.com/zeit/next.js#dynamic-import and https://github.com/zeit/next.js/tree/canary/examples/with-dynamic-import

Thank you for pointing this out. It is certainly possible. Yet again one can't leverage code-splitting based on files in "pages" folder, it should be implemented on its own.

@baer

This comment has been minimized.

Copy link

@baer baer commented Dec 3, 2019

What about Next's static export would not work on a smart TV? Going by the proposal, since it was implemented in the latest canary build, you can use either Next.js's APIs or React's APIs to choose exactly when data fetching happens.

  • SSR/Static build and client render - getInitialProps
  • Static build only - getStaticProps
  • SSR only - getServerProps
  • Client render only - useEffect (or getDerivedStateFromProps in certain edge-cases)

In this sense, it seems like developing for a Smart TV is similar to deploying a dynamic client-rendered web-application to a CDN which is possible today through static export. I spent a few min digging through the Samsung docs but I didn't really know what APIs you were referring to in this issue so I may be off base.

@timneutkens

This comment has been minimized.

Copy link
Member

@timneutkens timneutkens commented Dec 3, 2019

getDerivedStateFromProps -> actually just useEffect in a component is more correct. getDerivedStateFromProps is for edge cases where normal state management doesn't work.

@grushetsky

This comment has been minimized.

Copy link
Contributor Author

@grushetsky grushetsky commented Dec 4, 2019

In this sense, it seems like developing for a Smart TV is similar to deploying a dynamic client-rendered web-application to a CDN which is possible today through static export.

@baer, it turns out I didn't realize how exactly next export works. For whatever reason, I supposed that the navigation between pages of an exported app leads to page reloading upon route change. As you all know that isn't the case, thus, an exported app should work fine.

I coined a Smart TV demo app that uses many of the features I previously mentioned (Link, dynamic routing, data fetching, custom _app.js, etc.) It runs on TV and I can navigate the app keeping the shared state across pages. Dynamic routing is broken though. I'm going to dig into this problem and check if other features work fine. If the problem is going to be resolved, I'll create a PR with an example of using Next.js for Tizen Smart TV app.

Thanks to everyone for the help!

@timneutkens, it is suggested to leave this issue opened until we come up with a sample app.

@baer

This comment has been minimized.

Copy link

@baer baer commented Dec 4, 2019

@grushetsky - I've found that some systems really don't like [] in paths. One of the many examples of this is lint-staged. I have no idea what the real issue is for smart TVs, but that's something worth exploring. Also, before the (awesome) Next.js team rolled out the filesystem-based dynamic routing, I was doing a lot of this by hand. I wrote up a PR and issue with all the gory details here. Hopefully, you'll be able to get things working as they are but, there are ways to do it yourself if you really need it.

@grushetsky

This comment has been minimized.

Copy link
Contributor Author

@grushetsky grushetsky commented Dec 4, 2019

@baer, thank you for the insight!

@thomasboulongne

This comment has been minimized.

Copy link

@thomasboulongne thomasboulongne commented Dec 4, 2019

I have a similar request, for a different need. I'm making a static website, building and exporting on content change from my CMS for production.
However I'd like to setup a preview site to reflect dynamic changes without triggering a build on every content change, as I don't need pre-rendering for this environment.
So I would like to just keep using getInitialProps instead of duplicating the code both in useEffect and getInitialProps.
I'm coming from the Vue environment and I think that this feature request is similar to the Nuxt SPA mode (https://nuxtjs.org/guide#single-page-applications-spa-), correct me if I'm wrong

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.