···5353Images added to pages can be transformed by using [`ctx.assets.add_image_with_options()`](https://docs.rs/maudit/latest/maudit/assets/struct.RouteAssets.html#method.add_image_with_options), which takes an additional [`ImageOptions`](https://docs.rs/maudit/latest/maudit/assets/struct.ImageOptions.html) struct to specify how the image should be processed.
54545555```rs
5656+use maud::html;
5657use maudit::route::prelude::*;
57585859#[route("/image")]
···6364 let image = ctx.assets.add_image_with_options(
6465 "path/to/image.jpg",
6566 ImageOptions {
6666- width: Some(800),
6767- height: None,
6868- format: Some(ImageFormat::Png)
6767+ width: Some(800),
6868+ height: None,
6969+ format: Some(ImageFormat::Png),
6970 },
7071 )?;
71727272- Ok(format!("<img src=\"{}\" alt=\"Processed Image\" />", image.url))
7373+ Ok(html! {
7474+ (image.render("My 800 pixel wide PNG"))
7575+ })
7376 }
7477}
7578```
+220
website/content/news/2026-in-the-cursed-lands.md
···11111212> Interested in trying out Maudit? Follow our [Quick Start](/docs/quick-start/) guide.
13131414+## Image processing
1515+1616+[Maudit 0.4.0](https://github.com/bruits/maudit/blob/main/crates/maudit/CHANGELOG.md#040) added support for image processing, allowing you to easily resize, convert and optimize images for your website at build-time.
1717+1818+```rs
1919+use maud::html;
2020+use maudit::route::prelude::*;
2121+2222+#[route("/image")]
2323+pub struct ImagePage;
2424+2525+impl Route for ImagePage {
2626+ fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
2727+ let image = ctx.assets.add_image_with_options(
2828+ "path/to/image.jpg",
2929+ ImageOptions {
3030+ width: Some(800),
3131+ height: None,
3232+ format: Some(ImageFormat::Png),
3333+ },
3434+ )?;
3535+3636+ Ok(html! {
3737+ (image.render("My 800 pixel wide PNG"))
3838+ })
3939+ }
4040+}
4141+```
4242+4343+See [our section on image processing](https://maudit.org/docs/images/#processing-images) for more information on how to use images in Maudit.
4444+4545+### Placeholders generation
4646+4747+Maudit also includes the ability to easily create low-quality image placeholders (LQIP) for your images using [ThumbHash](https://evanw.github.io/thumbhash/).
4848+4949+```rs
5050+use maudit::route::prelude::*;
5151+5252+#[route("/image")]
5353+pub struct ImagePage;
5454+5555+impl Route for ImagePage {
5656+ fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
5757+ let image = ctx.assets.add_image("path/to/image.jpg")?;
5858+ let placeholder = image.placeholder();
5959+6060+ Ok(format!("<img src=\"{}\" alt=\"Image with placeholder\" style=\"background-image: url('{}'); background-size: cover;\" />", image.url(), placeholder.data_uri()))
6161+ }
6262+}
6363+```
6464+6565+Check [our documentation on placeholders](/docs/images/#placeholders) for more information.
6666+6767+## Customizable Markdown rendering
6868+6969+[Maudit 0.5.0](https://github.com/bruits/maudit/blob/main/crates/maudit/CHANGELOG.md#050) added support for components and shortcodes in Markdown files. These features allows you to completely customize how your Markdown files are rendered and enhance them with cool new possibilities.
7070+7171+### Shortcodes
7272+7373+Embedding a Youtube video typically requires one to copy this long, ugly, iframe tag and configure the different attributes to make sure it renders properly, it'd be nice to have something more friendly, a code that would be short, you will.
7474+7575+```md
7676+Here's my cool video:
7777+7878+{{ youtube id="b_KfnGBtVeA" /}}
7979+```
8080+8181+```rs
8282+content_sources![
8383+ "articles" => glob_markdown_with_options::<ArticleContent>("content/articles/*.md", MarkdownOptions {
8484+ shortcodes: {
8585+ let mut shortcodes = MarkdownShortcodes::default();
8686+8787+ shortcodes.register("youtube", |attrs, _| {
8888+ if let Some(id) = attrs.get::<String>("id") {
8989+ format!(r#"<iframe width="560" height="315" src="https://www.youtube.com/embed/{}" frameborder="0" allowfullscreen></iframe>"#, id)
9090+ } else {
9191+ panic!("YouTube shortcode requires an 'id' attribute");
9292+ }
9393+ });
9494+9595+ shortcodes
9696+ },
9797+ ..Default::default()
9898+ })
9999+],
100100+```
101101+102102+For more information, read [our section on shortcodes](/docs/content/#shortcodes).
103103+104104+### Components
105105+106106+Sometimes, you want to be able to keep writing normal, spec-compliant, Markdown, but still be able to add a bit of spice to it. For this Maudit supports components, allowing you to use custom code when rendering normal Markdown elements.
107107+108108+For instance, you may want to add an anchor icon to every heading, without needing to use a `{{ heading }}` shortcode.
109109+110110+```rs
111111+use maudit::components::MarkdownComponents;
112112+113113+struct CustomHeading;
114114+115115+impl HeadingComponent for CustomHeading {
116116+ fn render_start(&self, level: u8, id: Option<&str>, _classes: &[&str]) -> String {
117117+ let id_attr = id.map(|i| format!(" id=\"{}\"", i)).unwrap_or_default();
118118+ let href = id.map(|i| format!("#{}", i)).unwrap_or_default();
119119+ format!(
120120+ "<div><a href=\"{href}\"><span aria-hidden=\"true\">{}</span></a><h{level}{id_attr}>", include_str("icons/anchor.svg")
121121+ )
122122+ }
123123+124124+ fn render_end(&self, level: u8) -> String {
125125+ format!("</h{level}></div>")
126126+ }
127127+}
128128+```
129129+130130+```rs
131131+content_sources![
132132+ "blog" => glob_markdown_with_options::<BlogPost>("content/blog/**/*.md", MarkdownOptions {
133133+ components: MarkdownComponents::new().heading(CustomHeading),
134134+ ..Default::default()
135135+ }),
136136+],
137137+```
138138+139139+For more information, read [our section on components](/docs/content/#components).
140140+141141+## Improved error handling
142142+143143+[Maudit 0.6.0](https://github.com/bruits/maudit/releases/tag/maudit-v0.6.0) and [0.6.6](https://github.com/bruits/maudit/releases/tag/maudit-v0.6.6) made it much easier to handle errors inside of pages by making all of the assets (which are quite prone to errors, filesystem and all) methods return Result instead of panicking.
144144+145145+Additionally, pages themselves can now optionally return `Result` and will bubble up their errors up the chain up to [the entrypoint](/docs/entrypoint/) when using the `?` operator. Maudit implements `Into<RenderResult>` for `Result<T: Into<RenderResult>, E: Error>`, as such using `?` and returning `Result` require no signature changes inside your pages.
146146+147147+```rs
148148+use maudit::route::prelude::*;
149149+use maud::html;
150150+151151+#[route("/example")]
152152+pub struct Example;
153153+154154+impl Route for Example {
155155+ fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
156156+ // Use the ? operator to bubble up asset-related errors
157157+ let logo = ctx.assets.add_image("images/logo.png")?;
158158+159159+ // Wrap your return value with Ok()
160160+ Ok(html! {
161161+ (logo)
162162+ p { "My cool logo!" }
163163+ })
164164+ }
165165+}
166166+```
167167+168168+Or, you can just `unwrap()` everything, that's ok! Check our section on [handling errors](/docs/routing/#handling-errors) if you'd like to learn more.
169169+170170+## Support for internationalization
171171+172172+[Maudit 0.7.0](https://github.com/bruits/maudit/releases/tag/maudit-v0.7.0) added support for internationalizating routes. For instance, you may want to have a `/about` in English, but `/a-propos` and `/om-oss` in French and Swedish respectively.
173173+174174+This is possible to do right now in Maudit: You can duplicate your `About` struct twice, register the two new routes, rewrite the `render` implementation twice.. but that's a bit cumbersome, so Maudit now allows you to generate all these pages using a single struct:
175175+176176+```rust
177177+use maudit::route::prelude::*;
178178+179179+#[route(
180180+ "/contact",
181181+ locales(sv(prefix = "/sv"), de(path = "/de/kontakt"))
182182+)]
183183+pub struct Contact;
184184+185185+impl Route for Contact {
186186+ fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
187187+ match &ctx.variant {
188188+ Some(language) => match language.as_str() {
189189+ "sv" => "Kontakta oss.",
190190+ "de" => "Kontaktieren Sie uns.",
191191+ _ => unreachable!(),
192192+ },
193193+ _ => "Contact us.",
194194+ }
195195+ }
196196+}
197197+```
198198+199199+The ergonomics are still a bit iffy, but this nonetheless already makes it much easier to localize your website. To learn more about internationalization [visit our documentation](/docs/routing/#internationalization-i18n).
200200+14201## Built-in sitemap generation
1520216203[Maudit 0.9.0](https://github.com/bruits/maudit/releases/tag/maudit-v0.9.0) added support for automatically generating a sitemap for your website. In this new world of AI and other advanced web crawlers, sitemaps are a bit of an old relic. However, they're still considered useful to ensure that search engines properly index your website.
···54241<span class="text-center block italic">Showing the Hover strategy for prefetching</span>
5524256243For more information on prefetching, see [our prefetching documentation](/docs/prefetching/).
244244+245245+## Redirect utilities
246246+247247+[Maudit 0.10.0](https://github.com/bruits/maudit/releases/tag/maudit-v0.10.0) also added a new `redirect()` function to... well, redirect to another page.
248248+249249+```rust
250250+use maudit::route::prelude::*;
251251+252252+#[route("/redirect")]
253253+pub struct Redirect;
254254+255255+impl Route for Redirect {
256256+ fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> {
257257+ redirect("https://example.com")
258258+259259+ // Use a page's url method to generate type safe links:
260260+ // redirect(&OtherPage.url(None))
261261+ }
262262+}
263263+```
264264+265265+Simple enough. The return value of this function can be directly used in your pages, making it nice and easy to redirect to new content. To learn more about internationalization [redirect yourself to our documentation](/docs/routing/#redirects).
266266+267267+## The future
268268+269269+Maudit is mightier than before, but there's still so many twisted paths we'd like to follow. Including, but not limited to:
270270+271271+- Ability to generate variants of pages, outside of the localization system.
272272+- Support for generating PWAs automatically
273273+- Built-in font support (w/ subsetting)
274274+- ... and more!
275275+276276+For now, we go back into hiding. See you soon!