postlight / headless-wp-starter

🔪 WordPress + React Starter Kit: Spin up a WordPress-powered React app in one step
https://archive.postlight.com/labs/wordpress-react-starter-kit
GNU General Public License v2.0
4.48k stars 649 forks source link

URL structure (without "/page/" and "/post/" ) #155

Closed tymoxx closed 2 years ago

tymoxx commented 5 years ago

I am migrating my Wordpress website to Next.js with the following URL structure: "example.com/awesome-post" "example.com/awesome-page"

How I can keep the URLs without "/post/" or "/page/", but load the correct post and page, respectively.

"example.com/awesome-post" instead of "example.com/post/awesome-post" "example.com/awesome-page" instead of "example.com/page/awesome-page"

rubenmarcus commented 5 years ago

@gopolar I think you cant, because the server wont know what is a page or post, cause they have same structure on root

You can try this on server.js , but i dont know if it will work

 server.get("/:post", (req, res) => {
            const actualPage = "/post";
            const queryParams = { post: req.params.post};
            app.render(req, res, actualPage, queryParams);
        });

 server.get("/:page", (req, res) => {
            const actualPage = "/page";
            const queryParams = { post: req.params.page};
            app.render(req, res, actualPage, queryParams);
        });
tymoxx commented 5 years ago

Maybe someone can help to achieve it by adding (or modifying) the API on the Wordpress side?

aristech commented 5 years ago

@gopolar It should work without modifications on the WP side but could be an issue only if a page and a post have the same slug. Wordpress can't distinguish one from another and will choose the higher hierarchy, which is page!

goranefbl commented 5 years ago

What @rubenmarcus said works. Check the server.js,post.js and page.js in this example. As you have page and post routes, such as /wp-json/wp/v2/pages/?slug=your-page-name-her and /wp-json/wp/v2/posts/?slug=your-page-name-her you should get correct data.

tymoxx commented 5 years ago

Hi @goranefbl, are you sure it works when you go to the page/post through the browsers URL (not by clicking the links)?

goranefbl commented 5 years ago

Actually, you are right...no idea why it worked for me on ssr which was during the refresh, probably forgot about caching....:/

Oh well, the only solution I see here is to create a custom route or use a plugin like this https://github.com/elevati/wp-api-multiple-posttype to enable query on both page and post via single route. Then route can give you both content and what post type it is (page or post), then use that type to load the template for a page or for a post.

tymoxx commented 5 years ago

I finally made a solution.

In api-routes.php I modified the function rest_get_post:

    function rest_get_post( WP_REST_Request $request ) {
        if ( get_page_by_path($request->get_param( 'slug' ))){
            $type = 'page';
        } else {
            $type = 'post';
        }
        return rest_get_content( $request, $type, __FUNCTION__ );
    }

server.js:

        server.get("/:slug", (req, res) => {
            const actualPage = "/post";
            const queryParams = { slug: req.params.slug, apiRoute: "post" };
            app.render(req, res, actualPage, queryParams);
        });

menu.js, inside the <Link> tag:

as={item.object === 'post' || item.object === 'page' ? `/${slug}` : `/${item.object}/${slug}`}

And remember to remove 'post/' and 'page/' in other pages (like index.js and category.js) in the as attributes for <Link>.

Triphys commented 5 years ago

Amazing solution @gopolar! Thanks a lot, helped me :) One question though, I have a page that needs a special template that cannot be page.js and so far I solved that using this:

server.js

server.get("/tjanster", (req, res) => {
    const actualPage = "/tjanster";
    const queryParams = { slug: req.params.slug, apiRoute: "tjanster" };
    app.render(req, res, actualPage, queryParams);
 });

api-routes

function rest_get_tjanster( WP_REST_Request $request ) {
    return rest_get_content( $request, 'tjanster', __FUNCTION__ );
}

But now when I try to access /tjanster it only gives me a 404, is there anything I could do to work around that?

wrongakram commented 5 years ago

@gopolar The solution worked in the previous version. However, the new update got rid of the api-routes.php file and this seems to still be unsolved.

modelm commented 5 years ago

Would an endpoint like https://github.com/jackreichert/a-wp-react-redux-theme/blob/master/lib/endpoints/pretty-permalinks.php solve this problem?

justinwhall commented 5 years ago

This is what I'm using with Next

  // server.js
  // Will match anything like: https://domain/whatever
  server.get('/:slug', (req, res) => {
    const actualPage = '/page';
    const queryParams = { slug: req.params.slug };
    app.render(req, res, actualPage, queryParams);
  })
// page.js
import React, { Component } from 'react';
import Query from '../lib/Query';
import App from '../components/App';
import withError from '../hoc/withError';

class Page extends Component {
  static async getInitialProps({
    query, req, res,
  }) {
    if (!query.slug && !req) {
      return false;
    }

    const slug = query.slug ? query.slug : req.params.slug;
    // Query is just an abstraction of Fetch. It does some error handling and normalizes response.
    const data = await Query(
      `/wp-json/littlebot/v1/page?slug=${slug}`,
      res,
    );

    return data;
  }

  render() {
    const { data } = this.props;

    return (
      <App>
        <div className="site__content">
          <article className="article">
            <h1>{data.title.rendered}</h1>
            <div dangerouslySetInnerHTML={{ __html: data.content.rendered }} />
          </article>
        </div>
      </App>
    );
  }
}

// withError is a HOC to check if there is an actual page.
export default withError(Page);
// withError.js
import React from 'react';
import ErrorPage from 'next/error';

export default Component => class WithError extends React.Component {
  static async getInitialProps(ctx) {
    let props = Component.getInitialProps ? await Component.getInitialProps(ctx) : false;

    if (!props) {
      props = {};
      props.statusCode = 404;
    }

    return props;
  }

  render() {
    if (this.props.statusCode !== 200) {
      return <ErrorPage statusCode={404} />;
    }

    return <Component {...this.props} />;
  }
};
alexiakl commented 2 years ago

Looks like tymoxx found a solution for this issue. Closing.