A couple of notes for my own reference on 404 pages when building Expression Engine websites.
While you can nominate a template to use for a 404 page, it only really covers top-level urls that just use a single segment - doesn't deal with urls 2 segments and above as it assumes it may be a segment_1 url that handles all sub-urls itself.
To get around this, we can add this to the page templates (or a shared include):
{!-- if it's not the homepage, and it's not the 404 page, then look for an entry that matches the url_title of the last
-- segment of the url, and if it doesnt exist then go to the 404 page instead
--}
{if segment_1 != '' && last_segment != "404"}
{exp:channel:entries disable="members|categories|pagination" url_title="{transcribe:last_segment}" dynamic="no"}
{if no_results}{redirect="/404"}{/if}
{/exp:channel:entries}
{/if}
(Note: if you have any pages that intentionally aren't generated from a CMS entry (eg a job description using an ATS plugin), then you'll need to add that as an exclusion in the if statement otherwise it'll go to the 404 page.)
You'll still need the above code in all the page templates, but you'll also want to create multi-lingual 404 pages that add a few complications.
You'll need to create a separate CMS page in each language for your 404 page content, call the pages uniquely with the language code, eg "/404-en", you could then within the 404 page template pull the page content out in {exp:channel:entries} or {exp:transcribe:entries} by using :
{exp:transcribe:entries
parse="inward"
channel="general"
disable="members|categories|pagination"
dynamic="no"
transcribe="disable"
url_title="404-{transcribe:language_abbreviation}"}
Note that this way, the 404 page urls will end up as /en/404
, but they now won't give the actual http/404 error code, to do
that you'll need to enable the expressionengine HTTP Headers addon (comes with expressionengine but disabled by default), and
pop {exp:http_header status="404"}
at the top of your 404 page template.