gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.27k stars 10.31k forks source link

[RFC] remove our special layout components in Gatsby v2 #3830

Closed KyleAMathews closed 6 years ago

KyleAMathews commented 6 years ago

I've found that multiple layouts is a constant source of confusion. It's a really advanced feature that very few people need. It mostly causes confusion from people trying to use it but not understanding how to use it so wondering why extra layout components they add don't work.

It also causes problems because if a site has multiple layouts, there's no one "entry" for the entire site where you can import e.g. global css.

Also 99% of sites only need one global layout.

The 1% that do truly need multiple layouts can easily solve this with React. Just conditionally render different headers or footers. Also there's no performance problem with this as you can lazy load components needed for different sections of the site.

Removing multiple layouts support would also conveniently remove a lot of complicated code.

Has anyone used multiple layouts on a site and consider them essential to that project?

Nick-vr commented 6 years ago

No

rossipedia commented 6 years ago

Has anyone used multiple layouts on a site and consider them essential to that project?

Nope.

Nuke it. Simpler is (generally) always better

coston commented 6 years ago

Has anyone used multiple layouts on a site and consider them essential to that project?

No.

jwarpenius commented 6 years ago

No

szimek commented 6 years ago

I'm using it right now. I've got content in multiple languages and I've got a layout per language where I load language data and translations:

// src/layouts/de.js
import React from 'react';
import { addLocaleData } from 'react-intl';
import localeData from 'react-intl/locale-data/de';

import Layout from './index';
import messages from '../../data/translations/de.json';

addLocaleData(localeData);

export default (props) => <Layout {...props} locale="de" messages={messages} />;
// src/layouts/en.js
import React from 'react';
import { addLocaleData } from 'react-intl';
import localeData from 'react-intl/locale-data/en';

import Layout from './index';
import messages from '../../data/translations/en.json';

addLocaleData(localeData);

export default (props) => <Layout {...props} locale="en" messages={messages} />;

and so on for a few other languages. In gatsby-node.js I do

posts.edges.map(({ node }) => {
  const id = node.contentfulid;
  const locale = node.node_locale;

  return createPage({
    path: `/${locale}/blog/posts/${id}`,
    layout: locale,
    component: path.resolve('./src/templates/post-page.jsx'),
    context: {
      id,
      locale,
    },
  });
});

Is it possible to do it with a single layout, so that I don't have to load language data and translations for all supported languages?

mattferderer commented 6 years ago

@szimek maybe I'm mistaken but would it be easier to have an array like const locales = ['en', 'de'] and loop through them during the CreatePages function? Your Layout could then just accept a messages and localeData as context?

szimek commented 6 years ago

@mattferderer Maybe ;) How can I pass data to layout context in createPage function?

IainIsCreative commented 6 years ago

Oh God, this is a tricky one...while I have yet to explore layouts more, I can see it's use in unique layouts. However, I think that in itself is more for templates than layouts.

I think that unique layouts are the minority, and modified footers and headers could change depending on template, I think.

jlengstorf commented 6 years ago

This could potentially be useful in advanced use cases (for example, if an agency or SaaS platform wanted to make a site manager a la WordPress Multisite or whatever it's called).

That being said, could it be extracted to a plug-in so that multiple layouts we're opt-in only? I agree that simplifying it would probably benefit the vast majority of users.

monsieurnebo commented 6 years ago

I'm working on a website using two page layouts. I first created two Gatsby layouts, but I end up using a home-made component dedicated to templating and used inside the pages. Why ? Because I needed to pass some data from the page, to the layout.

So... I don't have a definitive opinion about the multiple layout things, but:

It also causes problems because if a site has multiple layouts, there's no one "entry" for the entire site where you can import e.g. global css.

@KyleAMathews THIS is a real issue. Having a single entry point for the whole app would be a great feature. Having multiple entry points makes maintainability harder and increase the chances of doing a mistake.


I'm using it right now. I've got content in multiple languages and I've got a layout per language where I load language data and translations.

@szimek You could use only one layout on let the pages handle the i18n locales. Just make one page by language during build time (onCreatePage) and pass them the concerned language via pathContext.

szimek commented 6 years ago

@monsieurnebo I don't want to hijack this thread to discuss the details of adding react-intl to Gatsby apps, so we can move it to Discord, but how that would work exactly? In my index layout, which is used by these locale-specific layouts, I already have components that use e.g. FormattedMessage, FormattedDate and intl directly, so they already require intl to be injected into context via IntlProvider.

monsieurnebo commented 6 years ago

@szimek Let switch to Discord to avoid the "hijack" :)

thomask-gh commented 6 years ago

I don't know what this is so I'm probably not using it... πŸ˜…

@szimek I heard about plug-ins to build multilingual websites, might be a better approach. If you're interested, look for "i18n Gatsby" on the web πŸ˜‰

szimek commented 6 years ago

@thomaskuntzz Thanks, but I already got a working solution that uses multiple layouts 😏The question is how to achieve the same with just one.

gatsby-starter-default-i18n uses multiple layouts.

gatsby-plugin-i18n only creates routes and in its docs it also mentions: "you can use different layouts for each language if you want to".

There's also https://www.gatsbyjs.org/blog/2017-10-17-building-i18n-with-gatsby that doesn't mention layouts at all, so I guess it is possible to do using just one layout, but they are using react-i18next and not react-intl.

kripod commented 6 years ago

I have never used extra layouts before, but now I think that most of us would be very curious about an official guide for internationalizing websites with Gatsby.

jbolda commented 6 years ago

@KyleAMathews what are the pros for having a layout?

If layouts are the on table, an alternative thought night be to drop layouts completely. Not only has there been confusion with multiple layouts, but there is a not a The Right Wayβ„’ to use layouts. This means we keep trying to make them more flexible including pushing up props, rerendering on prop changes, using multiple layouts, etc. Flexibility is great, but I think this is just better suited to be done with react. It is already seeming to be turned into a higher order component. We can opt to take care of this with react, and simplify both the teaching and the maintaining code efforts. I suspect it is easier to optimize webpack, preloading code and caching if there is only one smart component.

Layouts are currently handy to import global css. If we scrap layouts, we can import css via the higher order component. Alternatively, it might be interesting to add an API to take in global css. With the new webpack, this could make chunking global code out for long term caching something that comes nearly out of the box.

If we axe layouts, we only have one place for "top level queries". This seems like it should make graphql a bit easier to grasp, and make fragments more important to use. Both which I think are worthwhile.

Lastly, we have had the on and off discussion regarding the idea of themes for a while. I think that idea is difficult to reason about when sites are composed in so many different ways. If we axe layouts, our ability to compose a site isn't hindered (because React), but I would surmise that many sites will be composed very similarly. Gatsby-image is great because it handles the annoying stuff, and does all the things that most people want. If everyone is using a higher order component for their nav and sidebar, it would be great to create a theme that sets global css, and gives you a handful of components to use. Essentially handling the "shell" aka the annoying parts for those of us with a poor affinity to design.

We could even abstract to the point that someone could bring markdown for the homepage and a folder of markdown blog posts. The combination of themes handles the components and templates. The theme (or meta themes composing multiple themes) could just be added as an npm package, and they get any updates to it. Being able to track a package rather than 20 is something requested multiple times with gatsby-starters. Essentially emulating the approachability of create-react-app.

Lots of thoughts I've been considering for a while. Hopefully the translation from brain to text holds true. Happy to hear other thoughts on the matter.

jlengstorf commented 6 years ago

To @jbolda's point, when I built my site with Gatsby, I didn't use a layout at all β€” I ended up just building a Layout component: https://github.com/jlengstorf/lengstorf.com/blob/master/src/components/Layout.js

(This is because I got carried away before reading the rest of the docs, not because I had any kind of opinions about how things should be done. πŸ˜…)

Is there anything in particular that an official Gatsby layout does that isn't possible with a Layout component like I built? If not, it could be worth dropping an extra abstraction altogether. If so, maybe we can add to the docs to point out what makes a layout special.

Redmega commented 6 years ago

Shouldn't html.js be used to import global scripts/styles/etc? πŸ˜•

pieh commented 6 years ago

Agree about removing layouts as they make things more complicated than they need to be by making it gatsby specific construct instead of utilizing general react solutions. Obviously we should then document clear migration path for projects that utilize multiple layouts.

On the side note - we don't have actual data to backup those 99% - 1% numbers, right? Maybe we should consider gathering statistics about usage of gatsby apis during build time so we could make more informed decisions? Difficult subject as that would need to be opt-in (IMO) and then those statistics would certainly be skewed.

jquense commented 6 years ago

I'm skeptical of the 99% number, I've used multiple layouts in all my gatsby sites...

Are they essential? No but neither is most of the defaults in Gatsby I've found that they provide a much better api tho for starter projects of specific sorts since you only need to add the layout file vs an if branch in one parent layout. I feel like it's a good feature for tools or template authors and doesn't get in the way of the single default api if you don't need it

Just my 2cents :)

resir014 commented 6 years ago

I posted this on Twitter but I'll paste it here as well.

I think multiple layouts should stay. They are still useful for websites w/ page layouts that differ wildly from default like e.g. if news sites/blogs want to create a longform article with a specialised theme.

I couldn't think of any other way to implement it other than using multiple layouts.

South-Paw commented 6 years ago

It also causes problems because if a site has multiple layouts, there's no one "entry" for the entire site where you can import e.g. global css.

The use cases I see for multiple layouts are that they don't reuse the same assets or css (hence why they are considered a different layout, in a different folder πŸ˜„). So this makes sense to me...

The 1% that do truly need multiple layouts can easily solve this with React. Just conditionally render different headers or footers. Also there's no performance problem with this as you can lazy load components needed for different sections of the site.

Removing multiple layouts support would also conveniently remove a lot of complicated code.

I currently use 2 layouts for my personal site and another company website I'm working on is intending to use the multiple layouts for monthly promo landing pages that are out of the regular website's flow.

While I agree you probably can conditionally render these scenarios, I'm pretty sure it'd require a lot more code than just leaving in the support for multiple layouts. We could also create these pages as one-offs but I wouldn't be looking forward to that...

Layouts seems like one of those things that has it's own time and place to be used... maybe it isn't what a good portion of people will use but when you come across a the time to use it you're glad you had it.

So sure, they're not 'essential' and you can work things without them, but I don't think Gatsby would be moving in the right direction by removing this feature and I disagree that'd it would somehow lead to less complicated code.

Maybe instead the feature should be better explained in the documentation and when/why/how it could be used?

jlengstorf commented 6 years ago

@resir014 @South-Paw I haven't looked at the source for layouts, so I don't know if this is possible, but couldn't we get all the benefits for minimal trade-off if there was a gatsby-plugin-multilayout that enabled the current multiple layouts behavior, and switched Gatsby v2 to a single-layout default?

This has the following benefits:

  1. No regression for existing multi-layout users except a plugin addition.
  2. Clearer communication that multi-layout is an advanced feature (by making it opt-in)
  3. Simplified docs for beginners
  4. Keeps with the idea of a lean Gatsby core with a robust plugin ecosystem for advanced use cases

We'll need @KyleAMathews to confirm that moving multi-layout to a plugin is feasible, but β€” assuming it's possible β€” does that seem like a win-win to everyone?

jquense commented 6 years ago

it'd probably be fine to move to a plugin but i'm not at all sure that's possible with the current api...

South-Paw commented 6 years ago

@jlengstorf that seems like a good idea for best of both worlds. I just don't want to see this functionality disappear completely from v2 as it really does have valid use cases and provides a valuable feature/point of difference. πŸ‘

Jaikant commented 6 years ago

Keep multi layouts and document it as an advanced feature.

Removing it would add additional complexities for large projects because of conditional rendering in the only layout available.

May need some mechanism though to inject common styles. But that's one time technical problem rather than a multiple use case problem.

sandys commented 6 years ago

Not having multiple layouts is a huge problem for all sites other than personal portfolio/blogs. If that is the usecase that gatsbyjs wants to chase, then that's your choice - but essentially its a signal that gatsby is not intended for anything bigger. If anything, this has to be made better (have a single way of doing it), more idiot-proof.

Let me explain - this feature is intended for marketers and not developers. Every half-decent (or even quarter-decent) content or ecommerce site has to create zillions of landing pages. For example "Valentine's Day Offers for your 70th anniversary!" versus "First Valentine's Day ?". The design, layout, usability requirements for each landing page is substantially different.

Ideally, you should be able to use the powerful layout tools inside Gatsbyjs or Hugo to build reusable templates. So that my marketing team can easily edit a markdown/yaml page, choose a template and have a new landing page!

Basically, multiple landing pages is what makes a CMS versus a blog.

Jekyll isnt too good at this - it was intended as a developer hosting blog pages on github pages. Hugo does this brilliantly - https://gohugo.io/templates/views/. Gatsbyjs is very hard to set up like this (actually this is something i asked for in early days of trying out hugo -> gatsbyjs). I'm not sure if the documentation is a little hard, or if the process itself is too complex in gatsby.

But this is NOT an "advanced feature" (tantamount to saying - its not super important). It is a central question in where do you see gatsbyjs evolving - does it stay a blog for developers ? or does it go towards a full-fledged CMS for marketers.

KyleAMathews commented 6 years ago

Thank you for everyone's comments! Really helped me think through things over the weekend and today. Some specific replies then an update on what I'm thinking.

@pieh @jquense I did make up that number... :-) I guess I've never used multiple layouts so extrapolated with bit of hyperbole on top.

@szimek thanks for bringing up the details of your i18n site. That helped me clarify one of the roles of layouts which is groups of pages need a way to specify common data and other dependencies which are on the critical path for loading. You can't lazy load translations as then components depending on the translations will break.

@jbolda & @jlengstorf & @pieh thanks for questioning the need for the abstraction altogether as it made me think back to why I added it in the first place.

Let's go back to first principles.

Gatsby thinks in terms of "pages". Which has a path and a React component responsible for rendering that page. Pages can import code and other things through webpack plus define a GraphQL query to pull in data from Gatsby's data layer.

I originally started Gatsby v1 without any "layout" concept. But added it (as I remember) as I wanted both a common way to add a header/footer to sites (mimicking a feature in Gatsby v0).

After v1 launched, we added support for GraphQL queries to layouts + multiple layouts https://github.com/gatsbyjs/gatsby/pull/1503

@jbolda & @jlengstorf & @pieh raise a good question in why do we have layouts at all instead of just using normal React composition and having a Layout component you use on all your pages.

Part of the reason is this was in v0 so I ported it over. I think I added it in v0 as it's a common idea in other static site generators. In retrospect I agree now with @jbolda & @jlengstorf & @pieh that it never made sense as composing React components directly is the native React way of doing things and superior.

Another reason is it's an explicit way to code split data & code that are necessary for sub-sections of pages. E.g. @szimek's i18n example β€” each language layout is an entry point for those subsets of pages.

But this isn't a great reason. Code splitting is great but a custom ad-hoc method doesn't make a lot of sense.

What do we want?

This is easy to do with vanilla React e.g.

// page-1.js
import React from 'react'
import GlobalLayout from '../components/global-layout'
import SkinnyLayout from '../components/skinny-layout'

export default () => (
  <GlobalLayout>
    <SkinnyLayout>
      <div>Skinny page</div>
    </SkinnyLayout>
  </GlobalLayout>
)
// page-2.js
import React from 'react'
import GlobalLayout from '../components/global-layout'
import WideLayout from '../components/wide-layout'

export default () => (
  <GlobalLayout>
    <WideLayout>
      <div>Skinny page</div>
    </WideLayout>
  </GlobalLayout>
)

The layout components are optional, you can nest as many as you need, etc. They'd easily solve problems like i18n and any other layout setup you can imagine.

The one place plain vanilla React layouts fall down is GraphQL data. A common thing layouts do is query global metadata e.g. the site name to put into the <head> using react-helmet.

Imagine if we had syntax something like the following:

import React from 'react'
import Helmet from 'react-helmet'

const metadata = require(graphql`
  siteMetdata { title }
`)

export default () => (
  <div>
    <Helmet title={metadata.siteMetadata.title} />
     Hello world
  </div>
)

With a bit of babel magic, we could turn the "require" into a something that leverages a special webpack loader which runs the query and returns the data as normal (btw, I haven't thought this part through thoroughly so please poke holes)

With this new ability to "require" data from a graphql query, it'd be easy to use normal React components as layouts and easily add data & code onto the critical path for all pages or sub-sections.

Also, in webpack 4, the CommonsChunk plugin is getting smarter so we should be able in the future to further fine tune further how we generate and load bundles.

Thoughts? Anything I'm missing? Questions

KyleAMathews commented 6 years ago

@sandys we're definitely not just a developer blog engine :-) The purpose of this issue wasn't to eliminate multiple "layouts" but to rethink how we're doing this so it's more React-native + less error prone as I'm tired of answering questions about it :-)

I don't really understand what that Hugo docs page is describing so I'd love it if you could open another issue where we discuss how to accomplish whatever Hugo is doing there in Gatsby. Would love to create a docs page teaching the technique for people coming over from Hugo!

sandys commented 6 years ago

Hi Kyle Thanks for making this position clear. We can now stop holding our collective breath !!

Hugo invents its own templating rules engine and a precedence order.i would go so far as to say that it is in some way inspired by WordPress.

Since you guys are building from a React base, it would not be too important to replicate it. However there are a few important points that need to be taken care of:

  1. Precedence order - I don't think Gatsbyjs does this (atleast I couldn't find any). It's important for layouts to have a precedence order. This is the first thing you should think about when you architect your layout engine - you will invariably have to face questions about "how do I exclude these pages or directories from X,etc etc"

  2. Directory structure - mental model of humans is to create nicely separated folders. You should have a powerful system of matching folder structure to templates. Graphql can potentially be very powerful here. Shameless plug: you will need https://github.com/gatsbyjs/gatsby/issues/3727

  3. Minification and asset pipeline - I may not use one css framework in all my layouts. If your asset pipeline is designed around minifying EVERYTHING together, then you are going to have performance problems. This becomes especially relevant if you're also doing cache busting urls.

Having played with a bunch of these, I believe if you get these three right

On 06-Feb-2018 12:16, "Kyle Mathews" notifications@github.com wrote:

@sandys https://github.com/sandys we're definitely not just a developer blog engine :-) The purpose of this issue wasn't to eliminate multiple "layouts" but to rethink how we're doing this so it's more React-native + less error prone as I'm tired of answering questions about it :-)

I don't really understand what that Hugo docs page is describing so I'd love it if you could open another issue where we discuss how to accomplish whatever Hugo is doing there in Gatsby. Would love to create a docs page teaching the technique for people coming over from Hugo!

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/gatsbyjs/gatsby/issues/3830#issuecomment-363326930, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEsU_5melMWclaYvd4EBNNAlVfvr78nks5tR_UqgaJpZM4R3wR8 .

KyleAMathews commented 6 years ago

Precedence order

I haven't needed to work with something like this before β€” you can already choose page templates easily enough with createPages. Is there something else we need? Would love to discuss this more in another issue :-)

Directory structure

Yeah, solving #3727 would be sweet.

CSS code splitting

We're waiting on webpack here to make CSS a 1st-class citizen so CSS splitting is natively handled https://webpack.js.org/vote/

sandys commented 6 years ago

I haven't needed to work with something like this before β€” you can already choose page templates easily enough with createPages. Is there something else we need? Would love to discuss this more in another issue :-)

im afraid that's not precedence order in its entirety. Do you "have" to specify the template for each page ? Or can I set it at a directory level. So I can choose to have a template for this page, but if not specified, then picks the template set at the parent directory... and if not specified, then its parent and so on. I'm sure I can write something in createpages to simulate this behavior, but then that's true for every feature! It will be good to have a set of sane conventions that gatsbyjs enforces. Ultimately, gatsbyjs has to make peace with the notion that a framework is going to be opinionated - and it should be!

We're waiting on webpack here to make CSS a 1st-class citizen so CSS splitting is natively handled https://webpack.js.org/vote/

well.... you can do this today. all you need is the asset pipeline to generate independently for each independent template. I'll take the punishment in exchange for the flexibility (I'm the one asking for independent templates, am I not!). When CSS splitting comes, this becomes 100X faster that's all.

Jaikant commented 6 years ago

The purpose of this issue wasn't to eliminate multiple "layouts" but to rethink how we're doing this so it's more React-native + less error prone as I'm tired of answering questions about it :-)

I like this idea!

The top 3 things I like about Gatsby are:

  1. The ability to pull data from anywhere and use GraphQL to hydrate my react components.
  2. Aligns with React best practices as the react ecosystem evolves.
  3. Gives me the best practices of webpack/babel, image processing etc out of the box!!

Gatsby's goodness comes from React, GraphQL and the out of the box optimisations!

sandys commented 6 years ago

This is me just throwing out an idea without understanding all the nuances about it - maybe split createpages into two : createpages and renderpages. renderpages only takes care of the template to pick for rendering a page. It has a sane default implementation (with conventions documented by gatsbyjs), but can obviously be overridden.

another advantage of splitting renderpages (if it can be done) is that this can work with the asset pipeline to create separate assets for each template. renderpages is where the implementation of #3727 gets used to implement a precedence order.

jbolda commented 6 years ago

The one place plain vanilla React layouts fall down is GraphQL data. A common thing layouts do is query global metadata e.g. the site name to put into the using react-helmet.

@KyleAMathews, I was also thinking about that as well, but didn't want to sidetrack the conversation too much! I think global data vs local data is very easy to reason about. I was thinking it may be worthwhile to embrace "global" graphql queries. We could specify them in Gatsby-node.js perhaps?

Now, keeping things React-y as possible, props is kind of the local data. React recently has made an effort to tighten up context. I think this would fit perfectly in this case. Global graphql just gets piped into context. It would allow you to grab it from anywhere in the tree.

On another note, based on @sandys's explanation, precedence order actually sounds like v0 templates.

sandys commented 6 years ago

@KyleAMathews https://github.com/kyleamathews, I was also thinking about that as well, but didn't want to sidetrack the conversation too much! I think global data vs local data is very easy to reason about. I was thinking it may be worthwhile to embrace "global" graphql queries. We could specify them in Gatsby-node.js perhaps?

Now, keeping things React-y as possible, props is kind of the local data. React recently has made an effort to tighten up context. I think this would fit perfectly in this case. Global graphql just gets piped into context. It would allow you to grab it from anywhere in the tree.

Is this a usecase for Apollo or Redux with Graphql. I personally don't think Gatsbyjs should reinvent the wheel for global queries.

I don't join that Gatsbyjs should definitely build in global support on top of Apollo or Redux and integrate with Graphql.

Perhaps this is a new issue ?

KyleAMathews commented 6 years ago

@sandys yes β€” please start new issues if you want to talk about something new

KyleAMathews commented 6 years ago

@jquense what do you think about the idea of introducing a new graphql loader for webpack?

dirkdevriendt commented 6 years ago

Maybe a bit late to the game, but I wanted to add a use case I didn't see in the discussion yet.

I am trying out gatsby for an almost entirely dynamic site, with user-specific data being pulled in from a cloud graphQL service, using apollo. The site uses two layouts; a neutral and simplified one for the non-authenticated, gatsby-built site and one with a different, personalized theme, once the user is logged in.

I'm sure this will be solvable in some other way, though (it's components all the way down, after all :-)) so I'm not lobbying for keeping layouts. Having hooks in place that allow for (both build- and runtime) graphQL-based customisation of themes / css / <head> / ... is probably a more flexible and intuitive approach anyway.

pauldotknopf commented 6 years ago

I'd like to add something.

I am developing a CLI that uses Gatsby under the hood (for non technical users). The directory that the users will manage content in will contain only markdown files. All templates/styles/resources are provided via a npm package.

With the createLayout action, I can create a gatsby plugin that creates a layout that points to a component in my npm package.

I hope we can preserve this use case.

On a side note, I actually like layouts. I disagree with them being inherently confusing.

KyleAMathews commented 6 years ago

Sounds like a cool project!

Layouts would just be normal component. You'd just wrap your page component with the layout component of choice. If you want users to be able to select a layout component, you could add logic for that to your createPage calls to pick the right page component.

KyleAMathews commented 6 years ago

Hey y'all. Been thinking about this more and have a new proposal for the syntax for making static queries.

Previously I suggested a syntax like this:

const metadata = require(graphql`
  siteMetdata { title }
`)

With the idea being that we leverage webpack to do the hot reloading of data in development. But as @m-allanson's investigations are showing in https://github.com/gatsbyjs/gatsby/pull/4555, skipping webpack for data completely let's us do some crazy speedups.

So I went back to the drawing board and came up with the following:

import React from 'react'
import Helmet from 'react-helmet'
import { StaticQuery } from 'gatsby'

export default class ExampleComponent extends React.Component {
  render () {
    <StaticQuery
      query={graphql`
        {
           site {
             siteMetadata {
               title
             }
           }
       }
      `}
      render={staticData => (
        <div>
          <Helmet title={staticData.site.siteMetadata.title} />
          <h1>Welcome to {staticData.site.siteMetadata.title}!</h1>
          <p>We hope your stay is pleasant.</p>
        </div>
      )}
    />
  }
}

Just like with our current GraphQL implementation, we'd extract the graphql queries and run them for you. You'd be able to use fragments, etc. The only difference is you can't pass arguments to the query (hence the name, staticQuery :-)).

During development, we'd hot-reload changes to the query & underlying data.

Then in production, we'd do a cool optimization. We'd have a babel plugin which compiles the above into something that looks like:

import React from 'react'
import Helmet from 'react-helmet'
import { StaticData } from 'gatsby'
import staticData12513 from './staticData12513'

export default class ExampleComponent extends React.Component {
  render () {
    <StaticQuery
      data={staticData12513}
      render={staticData => (
        <div>
          <Helmet title={staticData.site.siteMetadata.title} />
          <h1>Welcome to {staticData.site.siteMetadata.title}!</h1>
          <p>We hope your stay is pleasant.</p>
        </div>
      )}
    />
  }
}

There's now a little packet of JSON attached to the component! Also, the static file name would be a hash of the query so on the off-chance you reuse a query, webpack 4 would split out the query result module into a new chunk that's shared between components.

I think this will work for all the existing layout use cases (tell if you see something this won't cover!).

It'll also enable new types of components that you can now attach not just markup, styles, and interactivity, but also bits of data. E.g. imagine a SEO component that queries standard data + you can pass in page specific data as well. Or a team member avatar component that has queried the name + image of the 10 team members so you can drop in the component anywhere it's needed. Or a product preview that you can manually add throughout the site as needed.

On the last idea β€” something I've thought would be cool is a way to "stamp out" lots of similar components.

There's now two ways to specify data requirements for a component. You can export a query "fragment" that you then mix-in to any page component query where you need the component and then take care of feeding the right data down to the sub-component. Which works well but requires some fiddly work each time you use the component.

Or, you could now directly specify the query for the component. The downside is that the query is static so for the case of avatars or product details, you'd either have to query all product info you might use and pass in a prop to specify the specific one to render or create duplicate components.

The later (creating lots of duplicate components) we could automate fairly easily.

Imagine a component for product details.

import React from 'react'
import { StaticQuery } from 'gatsby'

export default class ProductDetails extends React.Component {
  render () {
    <StaticQuery
      query={`graphql
        query ProductDetailsQuery($id: String!) {
          product(id: { eq: $id }) {
            title
            price
            description
          }
        }
     `}
      render={staticData => (
        <div>...</div>
      )}
     />
  }
}

We could then automatically stamp out identical components for each product ID β€” but crucially, we'd also extract out the actual component so that code wouldn't be duplicated.

Then you could just import each product details component like import ProductDetails123 from 'ProductDetails/123'.

Anyways, lots of details would need worked out there but wanted to toss it out as an idea as well.

shannonbux commented 6 years ago

Some more evidence that removing our special layout components from v2 might be a good idea: After running a 2-day Gatsby workshop last week, we heard this question from several people: "Does Gatsby force a layout component to every page without me having to import it?" They expected to have to import it, and were confused that they did not, since standard React components would need to be imported. @KyleAMathews

sandys commented 6 years ago

The reason why people are asking that is because of what I wrote a couple of posts up - defaults and preference order.

Most web app systems have defaults that are used if you don't override the layouts. This is everyone right from Rails to Hugo. So the confusion is stemming from - how do I get a different layout for this particular page since Gatsby is not letting me import a layout component .

React is simpler - there are no defaults. So if you don't import stuff, you don't get that available.

In general I think Gatsby is taking the approach of rails - to have defaults available without explicit imports, however it doesn't mean we can't override them.

What I'm concerned is that the graphql style of layout components won't actually work - because it's not that layout templates/components take the same data and render it differently. Templates would ask for different data in different templates.

So when you think of templates, you should think both ui and data together.

On Tue 17 Apr, 2018, 04:54 shannonbux, notifications@github.com wrote:

Some more evidence that removing our special layout components from v2 might be a good idea: After running a 2-day Gatsby workshop last week, we heard this question from several people: "Does Gatsby force a layout component to every page without me having to import it?" They expected to have to import it, and were confused that they did not, since standard React components would need to be imported. @KyleAMathews https://github.com/KyleAMathews

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/gatsbyjs/gatsby/issues/3830#issuecomment-381780522, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEsU9tvIR-iZytPPQjVD2j1IpRH_Ytrks5tpSgXgaJpZM4R3wR8 .

m-allanson commented 6 years ago

Thanks everyone for the great discussion here. This proposal was written up as RFC 0002 and implemented on the v2 branch in #4887.

There will be an upgrade guide (#3986), until then you can see this change applied to some of the example sites on the v2 branch - look for the ones with Gatsby dependencies set to next. Overall v2 progress can be tracked on the v2 project.

slorber commented 5 years ago

Hi,

I've opened an issue regarding code splitting and i18n files and it seems using layouts could be the best solution, like suggested by @szimek

I wonder if using something like that still works in v2 with the plugin:

createPage({
    path: `/fr/blog/posts/fr`,
    layout: "frLayout",
    component: path.resolve('./src/templates/post-page.jsx'),
})

Because this is not really documented and let me think we can only use one global layout at the same time.

@szimek have you migrated to v2?

szimek commented 5 years ago

@slorber Yeah, we did.

I don't remember where we found it, but we're using this HOC:

// src/lib/i18n/withIntl.js
import React from 'react';
import { IntlProvider, addLocaleData } from 'react-intl';
import { localeData } from '../locales';

addLocaleData(localeData);

export default (ComposedComponent) => {
  const withIntl = (props) => {
    const { pageContext } = props;
    const { locale } = pageContext;
    // eslint-disable-next-line global-require, import/no-dynamic-require
    const messages = require(`../../../../config/translations/${locale}.json`);

    return (
      <IntlProvider locale={locale} messages={messages}>
        <ComposedComponent {...props} />
      </IntlProvider>
    );
  };

  return withIntl;
};

Then we wrap each template in it:

// src/templates/PostPage.js
import { injectIntl } from 'react-intl';
import withIntl from '../lib/i18n/withIntl';

function PostPage({ data, intl }) {...}

export default withIntl(injectIntl(PostPage));

And use them in gatsby-node.js:

createPage({
  path: `${pathPrefix}/posts/${slug}/`,
  component: path.resolve('./src/templates/PostPage.js'),
  context: {
    locale,
    postId,
  },
});
slorber commented 5 years ago

Thanks @szimek

With your code, particularly this line import { localeData } from '../locales'; you end up having all your pages including all your translation files I think (see "bad solution 1" here) , which is not really what I'd like to have. Have you verified that this setup give you an optimized output with webpack bundle analyzer? I think you had an optimized setup in v1 and now it's not optimized anymore in v2

szimek commented 5 years ago

@slorber Unfortunately, you're right. After migrating to v2, we've started building the app separately for each language, so it's not really a big problem for us at the moment. However, ...

Now that assetPrefix option has been added, we could finally build the app just once for all languages and simplify our build process significantly. Unfortunately, like you said, in this case it looks like we'd be not only bundling translations for all languages (~50KB) (we don't really have that many of them, because most content comes from Contentful anyway), but also react-intl language data for all languages (~150KB). Also, though unrelated to i18n, pages-manifest.js file size grows to over 600KB, because it includes all pages in all languages :/ All file sizes are from minified, but uncompressed files.

I really hoped we'll be able to use assetPrefix, but I guess we'll keep building the app separately for each language now, until both i18n and pages-manifest.js issues are resolved :/

BTW. Have you tried gatsby-plugin-layout?