Utilo

XHTML MIME Type – Free, Online, Private

application/xhtml+xml is XHTML parsed as XML. A well-formedness error blanks the page. Ordinary HTML should stay text/html.

What .xhtml is

XHTML is HTML expressed as XML. The root element is in the XHTML namespace, the document is well-formed, and empty elements use the XML syntax. Served as application/xhtml+xml, a browser uses the XML parser. A single unescaped ampersand or a tag that does not close makes the page fail to render. Served as text/html, the same bytes go through the HTML parser and the XML rules you were careful about no longer apply. Polyglot markup tries to be both. Most sites should pick text/html and HTML syntax. This type is for a document you actually want parsed as XML.

IANA registers application/xhtml+xml. The media type is application/xhtml+xml. Extensions that use it on this page: .xhtml. A client compares the type, not the filename, once the response has arrived. The filename still matters on disk and in Content-Disposition, which is why a wrong extension and a wrong header are two different bugs.

What sends application/xhtml+xml

What sends application/xhtml+xml: Send application/xhtml+xml; charset=utf-8 only when the body is well-formed XHTML and the client is prepared for XML parsing. If you negotiate it against text/html on one URL, Vary: Accept is mandatory. A cache that stores the XML parse result under the HTML type, or the reverse, corrupts the next client. Script and CSS still run. The XML parser's rules for the script element are stricter about escaping. Do not send this type for an error page you built with string concatenation unless you escape every value. The file on disk is still .xhtml. Once the response exists, a client trusts application/xhtml+xml, not the filename. If you do not have a reason to be on the XML parser, stay on text/html. The reasons that hold up are an XML pipeline, inline SVG and MathML with namespaces you control, or a client that requested this type. 'More correct' is not a reason if the pages stop rendering.

Content-Type for .xhtml

Send application/xhtml+xml; charset=utf-8. The charset parameter names the encoding of the body. Leaving it off forces the client to guess, and for several text types the registered default is not UTF-8. Do not send a charset on a redirect. The header belongs on the response that contains the bytes.

Content-TypeContent-Type: application/xhtml+xml; charset=utf-8

nginx, Apache, and Express

These snippets map .xhtml to application/xhtml+xml. nginx reads the types block when it picks a type from the URL's extension. A location that sets default_type, or an application that sets the header itself, overrides that map. Apache AddType applies in the directory context where you put it. The Express static example writes the header for files ending in .xhtml so a stale mime database in the framework cannot disagree with the type on this page.

nginx# /etc/nginx/mime.types, inside the types { } block
application/xhtml+xml       xhtml;
charset utf-8;
charset_types application/xhtml+xml;
ApacheAddType application/xhtml+xml .xhtml
AddCharset UTF-8 .xhtml
Expressimport express from "express";

const app = express();
app.use(express.static("public", {
  setHeaders(res, filePath) {
    if (filePath.endsWith(".xhtml")) {
      res.setHeader("Content-Type", "application/xhtml+xml; charset=utf-8");
    }
  },
}));

Pitfalls for application/xhtml+xml

Pitfalls that show up for application/xhtml+xml, and not for a neighboring type with a similar extension: The failure mode is a yellow XML error screen, or a blank page, on a document that 'worked' as text/html. Validate before you switch the header. Another failure is an HTML5 feature you used that is not well-formed XML, such as a boolean attribute written as a bare token. Another is a document that is XHTML and still contains an unquoted script that includes the characters ]]>. Another is sending application/xhtml+xml to a client that only accepts text/html, such as an older webview, because a framework defaulted to XML when it saw the .xhtml extension.

  • text/html and application/xhtml+xml select different parsers. They are not aliases.
  • A file named .html can be XHTML if the header says so, and a file named .xhtml can be ordinary HTML if you send text/html. The header wins.
  • Namespaces matter. An svg element in XHTML needs the SVG namespace or it is an unknown element.
  • Do not add a second charset in a meta tag that disagrees with the header.

Frequently asked questions

When should I use this instead of text/html?

When the document is well-formed XHTML and you want the XML parser. For ordinary pages, use text/html.

What happens if the XML is not well-formed?

The browser does not render the page. It shows a parse error. The HTML parser would have recovered.

Do I send a charset?

Yes. application/xhtml+xml; charset=utf-8, consistent with the XML declaration.

Does the extension decide the parser?

No. The Content-Type does. .xhtml is only a hint for the server's types file.