A little app to serve my photography from my personal website

perf improvements

ericwood.org 42f56414 5738837c

Waiting for spindle ...
+252 -66
+1
Cargo.lock
··· 1159 1159 "sqlx", 1160 1160 "thiserror 2.0.17", 1161 1161 "tokio", 1162 + "tower", 1162 1163 "tower-http", 1163 1164 "urlencoding", 1164 1165 ]
+2 -1
Cargo.toml
··· 17 17 sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] } 18 18 thiserror = "2.0.17" 19 19 tokio = { version = "1.47.1", features = ["rt-multi-thread"] } 20 - tower-http = { version = "0.6.6", features = ["fs"] } 20 + tower = "0.5.2" 21 + tower-http = { version = "0.6.6", features = ["fs", "set-header"] } 21 22 urlencoding = "2.1.3"
assets/.DS_Store

This is a binary file and will not be displayed.

-54
assets/app.css
··· 1 - * { 2 - box-sizing: border-box; 3 - } 4 - 5 - html, body { 6 - height: 100%; 7 - min-height: 100%; 8 - margin: 0; 9 - padding: 0; 10 - } 11 - 12 - body { 13 - display: flex; 14 - flex-direction: column; 15 - align-items: center; 16 - font-family: "Inter", sans-serif; 17 - } 18 - 19 - .container { 20 - width: 100%; 21 - max-width: 3000px; 22 - overflow: auto; 23 - } 24 - 25 - header { 26 - display: flex; 27 - align-items: center; 28 - gap: 30px; 29 - font-size: 16pt; 30 - width: 100%; 31 - border-bottom: solid black 3px; 32 - } 33 - 34 - header h1 { 35 - text-transform: uppercase; 36 - font-weight: 900; 37 - margin: 0; 38 - letter-spacing: 3px; 39 - padding: 10px 30px; 40 - border-right: solid black 3px; 41 - } 42 - 43 - header nav ul { 44 - display: flex; 45 - gap: 30px; 46 - list-style-type: none; 47 - margin: 0; 48 - padding: 0; 49 - } 50 - 51 - header nav ul a { 52 - text-decoration: none; 53 - color: black; 54 - }
assets/fonts/.DS_Store

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Black.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-BlackItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Bold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-BoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-ExtraBold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-ExtraBoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-ExtraLight.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-ExtraLightItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Italic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Light.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-LightItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Medium.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-MediumItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Regular.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-SemiBold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-SemiBoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-Thin.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/Inter-ThinItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Black.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-BlackItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Bold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-BoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-ExtraBold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-ExtraBoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-ExtraLight.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-ExtraLightItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Italic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Light.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-LightItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Medium.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-MediumItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Regular.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-SemiBold.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-SemiBoldItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-Thin.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterDisplay-ThinItalic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterVariable-Italic.woff2

This is a binary file and will not be displayed.

assets/fonts/inter/InterVariable.woff2

This is a binary file and will not be displayed.

+148
assets/fonts/inter/inter.css
··· 1 + /* Variable fonts usage: 2 + :root { font-family: "Inter", sans-serif; } 3 + @supports (font-variation-settings: normal) { 4 + :root { font-family: "InterVariable", sans-serif; font-optical-sizing: auto; } 5 + } */ 6 + @font-face { 7 + font-family: InterVariable; 8 + font-style: normal; 9 + font-weight: 100 900; 10 + font-display: swap; 11 + src: url("InterVariable.woff2") format("woff2"); 12 + } 13 + @font-face { 14 + font-family: InterVariable; 15 + font-style: italic; 16 + font-weight: 100 900; 17 + font-display: swap; 18 + src: url("InterVariable-Italic.woff2") format("woff2"); 19 + } 20 + 21 + /* static fonts */ 22 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 100; font-display: swap; src: url("Inter-Thin.woff2") format("woff2"); } 23 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 100; font-display: swap; src: url("Inter-ThinItalic.woff2") format("woff2"); } 24 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 200; font-display: swap; src: url("Inter-ExtraLight.woff2") format("woff2"); } 25 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 200; font-display: swap; src: url("Inter-ExtraLightItalic.woff2") format("woff2"); } 26 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 300; font-display: swap; src: url("Inter-Light.woff2") format("woff2"); } 27 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 300; font-display: swap; src: url("Inter-LightItalic.woff2") format("woff2"); } 28 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("Inter-Regular.woff2") format("woff2"); } 29 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 400; font-display: swap; src: url("Inter-Italic.woff2") format("woff2"); } 30 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("Inter-Medium.woff2") format("woff2"); } 31 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 500; font-display: swap; src: url("Inter-MediumItalic.woff2") format("woff2"); } 32 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("Inter-SemiBold.woff2") format("woff2"); } 33 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 600; font-display: swap; src: url("Inter-SemiBoldItalic.woff2") format("woff2"); } 34 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("Inter-Bold.woff2") format("woff2"); } 35 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 700; font-display: swap; src: url("Inter-BoldItalic.woff2") format("woff2"); } 36 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 800; font-display: swap; src: url("Inter-ExtraBold.woff2") format("woff2"); } 37 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 800; font-display: swap; src: url("Inter-ExtraBoldItalic.woff2") format("woff2"); } 38 + @font-face { font-family: "Inter"; font-style: normal; font-weight: 900; font-display: swap; src: url("Inter-Black.woff2") format("woff2"); } 39 + @font-face { font-family: "Inter"; font-style: italic; font-weight: 900; font-display: swap; src: url("Inter-BlackItalic.woff2") format("woff2"); } 40 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 100; font-display: swap; src: url("InterDisplay-Thin.woff2") format("woff2"); } 41 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 100; font-display: swap; src: url("InterDisplay-ThinItalic.woff2") format("woff2"); } 42 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLight.woff2") format("woff2"); } 43 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2"); } 44 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 300; font-display: swap; src: url("InterDisplay-Light.woff2") format("woff2"); } 45 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 300; font-display: swap; src: url("InterDisplay-LightItalic.woff2") format("woff2"); } 46 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 400; font-display: swap; src: url("InterDisplay-Regular.woff2") format("woff2"); } 47 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 400; font-display: swap; src: url("InterDisplay-Italic.woff2") format("woff2"); } 48 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 500; font-display: swap; src: url("InterDisplay-Medium.woff2") format("woff2"); } 49 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 500; font-display: swap; src: url("InterDisplay-MediumItalic.woff2") format("woff2"); } 50 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBold.woff2") format("woff2"); } 51 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2"); } 52 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 700; font-display: swap; src: url("InterDisplay-Bold.woff2") format("woff2"); } 53 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 700; font-display: swap; src: url("InterDisplay-BoldItalic.woff2") format("woff2"); } 54 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBold.woff2") format("woff2"); } 55 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2"); } 56 + @font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 900; font-display: swap; src: url("InterDisplay-Black.woff2") format("woff2"); } 57 + @font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 900; font-display: swap; src: url("InterDisplay-BlackItalic.woff2") format("woff2"); } 58 + 59 + @font-feature-values InterVariable { 60 + @character-variant { 61 + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; 62 + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; 63 + alt-1: 1; /* Alternate one */ 64 + alt-3: 9; /* Flat-top three */ 65 + open-4: 2; /* Open four */ 66 + open-6: 3; /* Open six */ 67 + open-9: 4; /* Open nine */ 68 + lc-l-with-tail: 5; /* Lower-case L with tail */ 69 + simplified-u: 6; /* Simplified u */ 70 + alt-double-s: 7; /* Alternate German double s */ 71 + uc-i-with-serif: 8; /* Upper-case i with serif */ 72 + uc-g-with-spur: 10; /* Capital G with spur */ 73 + single-story-a: 11; /* Single-story a */ 74 + compact-lc-f: 12; /* Compact f */ 75 + compact-lc-t: 13; /* Compact t */ 76 + } 77 + @styleset { 78 + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; 79 + open-digits: 1; /* Open digits */ 80 + disambiguation: 2; /* Disambiguation (with zero) */ 81 + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ 82 + round-quotes-and-commas: 3; /* Round quotes & commas */ 83 + square-punctuation: 7; /* Square punctuation */ 84 + square-quotes: 8; /* Square quotes */ 85 + circled-characters: 5; /* Circled characters */ 86 + squared-characters: 6; /* Squared characters */ 87 + } 88 + } 89 + @font-feature-values Inter { 90 + @character-variant { 91 + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; 92 + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; 93 + alt-1: 1; /* Alternate one */ 94 + alt-3: 9; /* Flat-top three */ 95 + open-4: 2; /* Open four */ 96 + open-6: 3; /* Open six */ 97 + open-9: 4; /* Open nine */ 98 + lc-l-with-tail: 5; /* Lower-case L with tail */ 99 + simplified-u: 6; /* Simplified u */ 100 + alt-double-s: 7; /* Alternate German double s */ 101 + uc-i-with-serif: 8; /* Upper-case i with serif */ 102 + uc-g-with-spur: 10; /* Capital G with spur */ 103 + single-story-a: 11; /* Single-story a */ 104 + compact-lc-f: 12; /* Compact f */ 105 + compact-lc-t: 13; /* Compact t */ 106 + } 107 + @styleset { 108 + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; 109 + open-digits: 1; /* Open digits */ 110 + disambiguation: 2; /* Disambiguation (with zero) */ 111 + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ 112 + round-quotes-and-commas: 3; /* Round quotes & commas */ 113 + square-punctuation: 7; /* Square punctuation */ 114 + square-quotes: 8; /* Square quotes */ 115 + circled-characters: 5; /* Circled characters */ 116 + squared-characters: 6; /* Squared characters */ 117 + } 118 + } 119 + @font-feature-values InterDisplay { 120 + @character-variant { 121 + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; 122 + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; 123 + alt-1: 1; /* Alternate one */ 124 + alt-3: 9; /* Flat-top three */ 125 + open-4: 2; /* Open four */ 126 + open-6: 3; /* Open six */ 127 + open-9: 4; /* Open nine */ 128 + lc-l-with-tail: 5; /* Lower-case L with tail */ 129 + simplified-u: 6; /* Simplified u */ 130 + alt-double-s: 7; /* Alternate German double s */ 131 + uc-i-with-serif: 8; /* Upper-case i with serif */ 132 + uc-g-with-spur: 10; /* Capital G with spur */ 133 + single-story-a: 11; /* Single-story a */ 134 + compact-lc-f: 12; /* Compact f */ 135 + compact-lc-t: 13; /* Compact t */ 136 + } 137 + @styleset { 138 + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; 139 + open-digits: 1; /* Open digits */ 140 + disambiguation: 2; /* Disambiguation (with zero) */ 141 + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ 142 + round-quotes-and-commas: 3; /* Round quotes & commas */ 143 + square-punctuation: 7; /* Square punctuation */ 144 + square-quotes: 8; /* Square quotes */ 145 + circled-characters: 5; /* Circled characters */ 146 + squared-characters: 6; /* Squared characters */ 147 + } 148 + }
+32 -5
src/main.rs
··· 1 - use axum::{Router, response::Html, routing::get}; 1 + use axum::{ 2 + Router, 3 + http::{HeaderValue, header}, 4 + response::Html, 5 + routing::get, 6 + }; 2 7 use dotenvy::dotenv; 3 8 use minijinja_autoreload::AutoReloader; 4 9 use std::{env, sync::Arc}; 10 + use tower::ServiceBuilder; 5 11 mod app_error; 6 12 use app_error::AppError; 7 13 mod date_time; ··· 12 18 use models::{Photo, Tag}; 13 19 use sqlx::SqlitePool; 14 20 use templates::load_templates_dyn; 15 - use tower_http::services::ServeDir; 21 + use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer}; 16 22 17 23 struct AppState { 18 24 reloader: AutoReloader, ··· 29 35 let reloader = load_templates_dyn(); 30 36 let app_state = Arc::new(AppState { reloader, pool }); 31 37 let app = Router::new() 32 - .nest_service("/photos/assets", ServeDir::new(&env::var("ASSETS_PATH")?)) 38 + .nest_service( 39 + "/photos/assets", 40 + ServiceBuilder::new() 41 + .layer(SetResponseHeaderLayer::overriding( 42 + header::CACHE_CONTROL, 43 + HeaderValue::from_static("public, max-age=31536000, immutible"), 44 + )) 45 + .service(ServeDir::new(&env::var("ASSETS_PATH")?)), 46 + ) 33 47 .nest_service( 34 48 "/photos/thumbnails", 35 - ServeDir::new(&env::var("THUMBNAIL_PATH")?), 49 + ServiceBuilder::new() 50 + .layer(SetResponseHeaderLayer::overriding( 51 + header::CACHE_CONTROL, 52 + HeaderValue::from_static("public, max-age=31536000, immutible"), 53 + )) 54 + .service(ServeDir::new(&env::var("THUMBNAIL_PATH")?)), 36 55 ) 37 - .nest_service("/photos/images", ServeDir::new(&env::var("IMAGE_PATH")?)) 56 + .nest_service( 57 + "/photos/images", 58 + ServiceBuilder::new() 59 + .layer(SetResponseHeaderLayer::overriding( 60 + header::CACHE_CONTROL, 61 + HeaderValue::from_static("public, max-age=31536000, immutible"), 62 + )) 63 + .service(ServeDir::new(&env::var("IMAGE_PATH")?)), 64 + ) 38 65 .route("/photos", get(routes::photos::index)) 39 66 .route("/photos/{id}", get(routes::photos::show)) 40 67 .with_state(app_state)
+63 -5
templates/layout.jinja
··· 1 1 <!doctype html> 2 - <html land="en"> 2 + <html lang="en"> 3 3 <head> 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width,initial-scale=1.0"> 6 - <link rel="preconnect" href="https://fonts.googleapis.com"> 7 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 8 - <link href="https://fonts.googleapis.com/css2?family=Cousine:ital,wght@0,400;0,700;1,400;1,700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet"> 6 + <link re="stylesheet" href="/photos/assets/fonts/inter/inter.css"> 7 + <style type="text/css"> 8 + :root { font-family: 'Inter', sans-serif; } 9 + @supports (font-variation-settings: normal) { 10 + :root { font-family: 'InterVariable', sans-serif; } 11 + } 12 + </style> 13 + <style type="text/css"> 14 + * { 15 + box-sizing: border-box; 16 + } 17 + 18 + html, body { 19 + height: 100%; 20 + min-height: 100%; 21 + margin: 0; 22 + padding: 0; 23 + } 24 + 25 + body { 26 + display: flex; 27 + flex-direction: column; 28 + align-items: center; 29 + } 30 + 31 + .container { 32 + width: 100%; 33 + max-width: 3000px; 34 + overflow: auto; 35 + } 36 + 37 + header { 38 + display: flex; 39 + align-items: center; 40 + gap: 30px; 41 + font-size: 16pt; 42 + width: 100%; 43 + border-bottom: solid black 3px; 44 + } 45 + 46 + header h1 { 47 + text-transform: uppercase; 48 + font-weight: 900; 49 + margin: 0; 50 + letter-spacing: 3px; 51 + padding: 10px 30px; 52 + border-right: solid black 3px; 53 + } 54 + 55 + header nav ul { 56 + display: flex; 57 + gap: 30px; 58 + list-style-type: none; 59 + margin: 0; 60 + padding: 0; 61 + } 62 + 63 + header nav ul a { 64 + text-decoration: none; 65 + color: black; 66 + } 67 + </style> 9 68 <title> 10 69 {% block title %}{% endblock %} 11 70 </title> 12 71 {% block head %}{% endblock %} 13 - <link rel="stylesheet" href="/photos/assets/app.css" /> 14 72 </head> 15 73 <body> 16 74 <div class="container">
+6 -1
templates/photos/index.jinja
··· 18 18 data-width="{{ photo.width }}" 19 19 data-height="{{ photo.height }}" 20 20 > 21 - <img src="/photos/thumbnails/{{ photo.id }}.webp"> 21 + <img 22 + {% if loop.index < 6 %} 23 + fetchpriority="high" 24 + {% endif %} 25 + src="/photos/thumbnails/{{ photo.id }}.webp" 26 + > 22 27 </a> 23 28 {% endfor %} 24 29 </div>