Yew Router doesn't match any routes

443 views Asked by At

I'm attempting to build a SPA using Yew, and have followed this example. When I select links, the URL changes and the page renders correctly. (Yay!) But if I enter the URL, it returns 404. So for example, if I select the link for "History," the browser renders the FerrySpotList component and the indicated URL changes to "https://example.com/history". But if I enter "https://example.com/history" in the address bar and hit "enter," it returns the nginx 404. Interestingly, it won't even give me the custom 404 error that I've written -- going to "https://example.com/404" should give me the NotFound route, but instead just goes to the nginx 404 page.

Here's my minimal reproducible example; I've pulled out the code for the various components in the name of brevity. It's pretty much just a copy-paste from the example in the docs, and it half-works, so I'm baffled as to why it doesn't all-work.

Update: The real issue, as identified almost immediately by Cerberus, is that I'm serving the wasm with nginx, not trunk. Trunk doesn't offer any way to serve https, so I'm just exporting the compiled wasm to nginx to serve. The code is fine, it's the deployment that's wrong.

What's the right way to deploy Yew in production? The solution that comes to mind is to just use nginx as a reverse proxy for a trunk server. Am I way off base there?

#[derive(Clone, Routable, PartialEq, Debug, Copy)]
enum Route {
    #[at("/")]
    Home,
    #[at("/history")]
    History,
    #[at("/blog")]
    Blog,
    #[at("/post/:id")]
    PostID { id: u8 },
    #[not_found]
    #[at("/404")]
    NotFound,
}

fn main() {
   wasm_logger::init(wasm_logger::Config::default());
   yew::Renderer::<Main>::new().render();
}

#[function_component]
fn Main() -> Html {
    html! {
        <BrowserRouter>
            <Switch<Route> render={switch} />
        </BrowserRouter>
    }
}

fn switch(routes: Route) -> Html {
    match routes {
        Route::Home => html! { <FerryRouteComp /> },
        Route::History => html! { <FerrySpotList /> },
        Route::Blog => html! { <div>{"Blog Coming Soon"}</div>},
        Route::NotFound => html! {<p>{"Ooh.  I actually don't know where that page went.  Maybe start at <a href='https://ferrywizard.com'>home</a> and try again?"}</p>},
        Route::PostID { id } => html! {<p>{format!("Looking at ID #: {}", id)}</p>},
    }
}
1

There are 1 answers

2
djmcmath On BEST ANSWER

Ok, so the correct answer has nothing to do with my above-listed Router code, which is fine. Nor is the correct answer to use NginX as a proxy to a Trunk server (which I described above, and which works, but seems to be a suboptimal solution).

The correct answer starts with the Yew docs. The key sentence is "If the application uses the Yew router, you must configure the server to return the index.html when asked for a file that it does not have."

In plain English, for people like me who are just learning all of this, that means that: as a last resort, before your server delivers a 404, it should try "/index.html". So whichever server you're using, find the line where it says "Try this, then that, then give up and send the user to 404," and edit that one line.

Here's the relevant tidbit from my nginx.conf:

location / {
    root /var/www/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html =404;
}

Really, the only change I needed to make was to add "/index.html" to the appropriate line in my nginx.conf file. BTW, that leading "/" is important. If you enter, say, "example.com/blog", it'll try "example.com/blog/index.html," which doesn't exist. You need to send it to "example.com/index.html" when it can't find "/blog", which routes it appropriately to the index.html, which loads the Router, which redirects to the blog page. Phew!

Hope this saves someone else some time figuring out how to deliver a single page app. :)