Create new front page and include archive articles

Hey,
I’d like to create a new front page in my theme that is different from any archive page, but which includes recently created articles.

My first step was to add a new homeHandler to index.js:

const homeHandler = {
  name: "home",
  pattern: "/",
  func: ({ state }) => {
    state.source.data["/"].isFrontPage = true;
    state.source.data["/"].isArchive = false;
  }
}

…register it using
libraries.source.handlers.push(homeHandler);
in actions.theme.init and implement the component.

This works fine. But now, I would like to show the newest articles in my home page component. Is there any shorter way to achieve that without implementing a separate fetching logic (calling libraries.source.api.get and populating)? Right now, the data in state.source.get("/") is missing of course and I need to do basically the same which the frontity archive handler does.

Any ideas, information and best practices are welcome :slight_smile:
Thank you!

Hi @cobra! Sorry for the late reply

If I understood the case correctly you don’t need a handler for this. I think the best way would be to check if you are on the homepage (this info is already in Frontity state) and in that case, load a new component.

At the index.js file of your theme you should check if it’s the homepage, and if so load your <Home /> component:

const Theme = ({ state }) => {

  return (
    <>
      ...
      <Main>
        {(data.isFetching && <Loading />) ||
          (data.isHome && <Home />) ||
          (data.isArchive && <List />) ||
          (data.isPostType && <Post />) ||
          (data.is404 && <Page404 />)}
      </Main>
    </>
  );
};

export default connect(Theme);

Apart from that, you should create your <Home /> component where you can load whatever you want, including the latest posts. You could do something like this:

import React from "react";
import { connect, styled } from "frontity";
import Item from "./list-item";

const Home = ({ state }) => {
  // Get the data of the latest posts.
  const data = state.source.get(state.router.link);

  return (
    <Container>
      <div>Some content before the posts</div>

      <div>
          <p>Load the posts here</p>
          <div>
              {data.items.map(({ type, id }) => {
                  const item = state.source[type][id];
                  // Render one Item component for each post.
                  return <Item key={item.id} item={item} />;
              })}
          </div>
      </div>

      <div>Some content after the posts</div>

    </Container>
  );
};

export default connect(Home);

This way, you’ll be importing the list-item that it’s already created (with its title, author, excerpt, etc), but you could also create a new one and import that instead.

That should be enough, let us know if it works in your case and if you have any other questions don’t hesitate to share them :relaxed:

2 Likes

Thank you really much, this solves my problem!
However, I’d like to do one additional thing: Right now, it’s possible to access URLs like /page/1 etc. Is it possible to (easily) prevent that with this solution? I know, it’s not the standard behaviour for a WP blog, but in my current state of the website I would like to restrict that. Thank you!

Not sure about this one, for sure @David can confirm if I am correct. What I thought was to use the parse method (explained here) to check if page is included and in that case return a 404. In your case it could be something like this:

import Page404 from "./page404";

const Home = ({ state, libraries }) => {
  const currentLink = state.router.link;
  // Get the data of the latest posts.
  const data = state.source.get(currentLink);
  // Get the page of the link.
  let { page } = libraries.source.parse(currentLink);

  if (page > 1) {
    return <Page404 />;
  } else {
    return (
      <div>
        <div>Some content before the posts</div>

        <div>
          <p>Load the posts here</p>
          <div>
            {data.items.map(({ type, id }) => {
              const item = state.source[type][id];
              // Render one Item component for each post.
              return <Item key={item.id} item={item} />;
            })}
          </div>
        </div>

        <div>Some content after the posts</div>
      </div>
    );
  }
};

export default connect(Home);

Of course, you could change the page404 for other content if you want. I think this would solve your problem, but tell us if that’s not the case.

1 Like

Thank you @SantosGuillamot! Sorry for answering that late. In my project, I wanted to completely avoid the possibility to access the main page by using URLs like /page/… - so I did some research how to check the initial page link and I found state.frontity.initialLink. So right now, I’ll check for state.frontity.initialLink != "/" and will return a 404 for the home page.

Ok, found out that this is stupid and initialLink is only the initial one when accessing the whole page.

My new solution:

  1. I’ve extended frontity router state by router.activePageLink in router/index.ts:
    router: { link: string; activePageLink: string; };

  2. Set “/” as initial active page link for state in tiny-router/index.ts:
    router: { link: "/", activePageLink: "/", autoFetch: true }

  3. Set active page link for router actions init and set in tiny-router/actions.ts:

      export const set: TinyRouter["actions"]["router"]["set"] = ({
          state,
          actions,
          libraries
      }) => (link): void => {
          state.router.activePageLink = link;
          // normalizes link
          if (libraries.source && libraries.source.normalize)
              link = libraries.source.normalize(link);
          //...
    
      export const init: TinyRouter["actions"]["router"]["init"] = ({
          state,
          actions,
          libraries
      }) => {
          if (state.frontity.platform === "server") {
              state.router.activePageLink = state.frontity.initialLink;
    
            // Populate the router info with the initial path and page.
            //...
    

I think it’s generally a good idea to save the actual page link in the state, since it seems that only the normalized version of the link is saved currently. What do you think?

Hey @cobra, interesting development.

I don’t quite understand what you mean by activePageLink. Could you please elaborate?

On the other hand, I’m working on a PR to add things like path and page to data: https://github.com/frontity/frontity/pull/253

That way you could do things like:

const data = state.source.get(state.router.link);
if (data.path === "/" && data.page > 1) {
  // Do something here...
}

or use data.path instead of state.router.link in components…

const data = state.source.get(state.router.link);
return (
  <Main>
    {data.Home && <Home link={data.path} />}
  </Main>
);

Would that help?

2 Likes

Thanks, will have a look at your PR.
Sorry, the naming was confusing. What I meant with activePageLink was the full original path including parameters and pages, AFAIK this is only provided in a normalized form after calling libraries.source.normalize. I didn’t want any normalized link or page. My use case is that I want to restrict access to my home page only via using / and not via /page/..., so the only possibility I saw there was to check for the whole path.

These are the names we are using in Frontity:

  • link: "/category/nature/page/3?param=value"
  • path: "/category/nature"
  • page: 3
  • query: { param: "value" }

Right now we are only exposing the link in data, but we are going to expose everything.

If you only have link, you can get the other values using libraries.source.parse(link).

If you have the path, page and query you can get the link using libraries.source.stringify({ path, page, query }).