This is a classical use case of refresh tokens. The way the flow will work:
User logs in, the backend issues a short-lived (~1 hour) JWT and a long-lived refresh token and sends them to the frontend.
The frontend sends the JWT for each API call while it's still valid
In the event that the JWT has expired, the frontend should then use the refresh token to get a new JWT AND and new refresh token (rotating refresh token - see https://www.rfc-editor.org/rfc/rfc6749#section-10.4).
If the refresh token expires, then the user has to login again.
The advantage of the above method is that you can keep your APIs secure, and have the user be signed in for as long as you want.
Why do we need a refresh token (as opposed to using the invalid JWT to get a new one)?
Once a JWT expires, semantically, it should be completely useless.. if you allow it to be used to get a new JWT, then you haven't really expired it.
If an attacker gets hold of the JWT, after its expiry, then cannot continue to use it.
What if an attacker steals the refresh token?
This is where the charm of rotating refresh tokens comes into play. When you change the refresh token on each use (and revoke the older one), this allows you to 1) vastly minimise the risk from theft 2) detect that a theft has happened! And once you detect that, you can go about revoking the whole session to keep that user safe.
This and other benefits are explained in detail in this blog post
A note about the confusion of refresh tokens in other discussions
Lots of places state that you should not send the refresh token to the frontend. This is true in the case of an OAuth flow - where a third party is issuing tokens for your system's use. In this case, the "frontend" is your system (your app's frontend and backend) and the "backend" is the third party. I realise this may be a little confusing, but I am happy to chat about it here (My handle is @rp)
Final note
I would like to say that you can go ahead and implement this flow yourself (do take care of numerous race conditions and network failure issues mentioned in the above blog post), or you can check out and use our solution that does exactly (and much more) what is mentioned above in this answer.
I hope this helps!