diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs
index d12bf43..3d30b39 100644
--- a/src/compiler/compiler.rs
+++ b/src/compiler/compiler.rs
@@ -9,9 +9,6 @@ use crate::document::document::Document;
 use crate::document::document::ElemReference;
 use crate::document::variable::Variable;
 
-use super::navigation::NavEntry;
-use super::navigation::Navigation;
-
 #[derive(Clone, Copy)]
 pub enum Target {
 	HTML,
@@ -133,80 +130,128 @@ impl Compiler {
 		result
 	}
 
-	pub fn navigation(&self, navigation: &Navigation, document: &dyn Document) -> String
-	{
-		let mut result = String::new();
-		match self.target()
-		{
-			Target::HTML => {
-				result += r#"<ul id="navbar">"#;
-
-				fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize)
-				{
-					let ent_path = ent.path.as_ref()
-						.map_or("#".to_string(),|path| path.clone());
-					result.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str());
-
-					if let Some(children) = ent.children.as_ref()
-					{
-						result.push_str("<ul>");
-						for (name, ent) in children
-						{
-							process(result, name, ent, depth+1);
-						}
-						result.push_str("</ul>");
-					}
-				}
-
-				for (name, ent) in &navigation.entries
-				{
-					process(&mut result, name, ent, 0);
-				}
-				
-
-				result += r#"</ul>"#;
-			},
-			_ => todo!("")
-		}
-		result
-	}
-
 	pub fn footer(&self, _document: &dyn Document) -> String {
 		let mut result = String::new();
 		match self.target() {
 			Target::HTML => {
 				result += "</body></html>";
 			}
-			Target::LATEX => todo!("")
+			Target::LATEX => todo!(""),
 		}
 		result
 	}
 
-	pub fn compile(&self, navigation: &Navigation, document: &dyn Document) -> String {
-		let mut out = String::new();
+	pub fn compile(&self, document: &dyn Document) -> CompiledDocument {
 		let borrow = document.content().borrow();
 
 		// Header
-		out += self.header(document).as_str();
-
-		// Navigation
-		out += self.navigation(navigation, document).as_str();
+		let header = self.header(document);
 
 		// Body
+		let mut body = String::new();
 		for i in 0..borrow.len() {
 			let elem = &borrow[i];
 
 			match elem.compile(self, document) {
-				Ok(result) => {
-					out.push_str(result.as_str())
-				}
+				Ok(result) => body.push_str(result.as_str()),
 				Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()),
 			}
 		}
 
 		// Footer
-		out += self.footer(document).as_str();
+		let footer = self.footer(document);
 
-		out
+		// Variables
+		let variables = document
+			.scope()
+			.borrow_mut()
+			.variables
+			.iter()
+			.map(|(key, var)| (key.clone(), var.to_string()))
+			.collect::<HashMap<String, String>>();
+
+		CompiledDocument {
+			input: document.source().name().clone(),
+			mtime: 0,
+			variables,
+			header,
+			body,
+			footer,
+		}
+	}
+}
+
+#[derive(Debug)]
+pub struct CompiledDocument {
+	/// Input path relative to the input directory
+	pub input: String,
+	/// Modification time (i.e seconds since last epoch)
+	pub mtime: u64,
+
+	// TODO: Also store exported references
+	// so they can be referenced from elsewhere
+	// This will also require rebuilding in case some exported references have changed...
+	/// Variables exported to string, so they can be querried later
+	pub variables: HashMap<String, String>,
+
+	/// Compiled document's header
+	pub header: String,
+	/// Compiled document's body
+	pub body: String,
+	/// Compiled document's footer
+	pub footer: String,
+}
+
+impl CompiledDocument {
+	pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) }
+
+	fn sql_table() -> &'static str {
+		"CREATE TABLE IF NOT EXISTS compiled_documents (
+			input TEXT PRIMARY KEY,
+			mtime INTEGER NOT NULL,
+			variables TEXT NOT NULL,
+			header TEXT NOT NULL,
+			body TEXT NOT NULL,
+			footer TEXT NOT NULL
+		);"
+	}
+
+	fn sql_get_query() -> &'static str { "SELECT * FROM compiled_documents WHERE input = (?1)" }
+
+	fn sql_insert_query() -> &'static str {
+		"INSERT OR REPLACE INTO compiled_documents (input, mtime, variables, header, body, footer) VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
+	}
+
+	pub fn init_cache(con: &Connection) -> Result<usize, rusqlite::Error> {
+		con.execute(Self::sql_table(), [])
+	}
+
+	pub fn from_cache(con: &Connection, input: &String) -> Option<Self> {
+		con.query_row(Self::sql_get_query(), [input], |row| {
+			Ok(CompiledDocument {
+				input: input.clone(),
+				mtime: row.get_unwrap::<_, u64>(1),
+				variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(),
+				header: row.get_unwrap::<_, String>(3),
+				body: row.get_unwrap::<_, String>(4),
+				footer: row.get_unwrap::<_, String>(5),
+			})
+		})
+		.ok()
+	}
+
+	/// Inserts [`CompiledDocument`] into cache
+	pub fn insert_cache(&self, con: &Connection) -> Result<usize, rusqlite::Error> {
+		con.execute(
+			Self::sql_insert_query(),
+			(
+				&self.input,
+				&self.mtime,
+				serde_json::to_string(&self.variables).unwrap(),
+				&self.header,
+				&self.body,
+				&self.footer,
+			),
+		)
 	}
 }
diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs
index 722d563..43dd8a8 100644
--- a/src/compiler/navigation.rs
+++ b/src/compiler/navigation.rs
@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 
-use crate::document::document::Document;
+use super::compiler::CompiledDocument;
+use super::compiler::Target;
 
 #[derive(Debug)]
 pub struct NavEntry {
@@ -14,7 +15,43 @@ pub struct Navigation {
 	pub(crate) entries: HashMap<String, NavEntry>,
 }
 
-pub fn create_navigation(docs: &Vec<Box<dyn Document>>) -> Result<Navigation, String> {
+impl Navigation {
+	pub fn compile(&self, target: Target) -> String {
+		let mut result = String::new();
+		match target {
+			Target::HTML => {
+				result += r#"<ul id="navbar">"#;
+
+				fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize) {
+					let ent_path = ent
+						.path
+						.as_ref()
+						.map_or("#".to_string(), |path| path.clone());
+					result
+						.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str());
+
+					if let Some(children) = ent.children.as_ref() {
+						result.push_str("<ul>");
+						for (name, ent) in children {
+							process(result, name, ent, depth + 1);
+						}
+						result.push_str("</ul>");
+					}
+				}
+
+				for (name, ent) in &self.entries {
+					process(&mut result, name, ent, 0);
+				}
+
+				result += r#"</ul>"#;
+			}
+			_ => todo!(""),
+		}
+		result
+	}
+}
+
+pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<Navigation, String> {
 	let mut nav = Navigation {
 		entries: HashMap::new(),
 	};
@@ -30,63 +67,57 @@ pub fn create_navigation(docs: &Vec<Box<dyn Document>>) -> Result<Navigation, St
 		let (cat, title, path) = match (cat, title, path) {
 			(Some(cat), Some(title), Some(path)) => (cat, title, path),
 			_ => {
-				println!(
-					"Skipping navigation generation for `{}`",
-					doc.source().name()
-				);
+				println!("Skipping navigation generation for `{}`", doc.input);
 				continue;
 			}
 		};
 
 		if let Some(subcat) = subcat {
 			// Get parent entry
-			let cat_name = cat.to_string();
-			let mut pent = match nav.entries.get_mut(cat_name.as_str()) {
+			let mut pent = match nav.entries.get_mut(cat.as_str()) {
 				Some(pent) => pent,
 				None => {
 					// Create parent entry
 					nav.entries.insert(
-						cat_name.clone(),
+						cat.clone(),
 						NavEntry {
-							name: cat_name.clone(),
+							name: cat.clone(),
 							path: None,
 							children: Some(HashMap::new()),
 						},
 					);
-					nav.entries.get_mut(cat_name.as_str()).unwrap()
+					nav.entries.get_mut(cat.as_str()).unwrap()
 				}
 			};
 
 			// Insert into parent
-			let subcat_name = subcat.to_string();
 			if let Some(previous) = pent.children.as_mut().unwrap().insert(
-				subcat_name.clone(),
+				subcat.clone(),
 				NavEntry {
-					name: subcat_name.clone(),
+					name: subcat.clone(),
 					path: Some(path.to_string()),
 					children: None,
 				},
 			) {
 				return Err(format!(
-					"Duplicate subcategory:\n{subcat:#?}\nclashes with:\n{previous:#?}"
+					"Duplicate subcategory:\n{subcat}\nclashes with:\n{previous:#?}"
 				));
 			}
 		} else {
 			// Get entry
-			let cat_name = cat.to_string();
-			let mut ent = match nav.entries.get_mut(cat_name.as_str()) {
+			let mut ent = match nav.entries.get_mut(cat.as_str()) {
 				Some(ent) => ent,
 				None => {
 					// Create parent entry
 					nav.entries.insert(
-						cat_name.clone(),
+						cat.clone(),
 						NavEntry {
-							name: cat_name.clone(),
+							name: cat.clone(),
 							path: None,
 							children: Some(HashMap::new()),
 						},
 					);
-					nav.entries.get_mut(cat_name.as_str()).unwrap()
+					nav.entries.get_mut(cat.as_str()).unwrap()
 				}
 			};
 
diff --git a/src/main.rs b/src/main.rs
index a19a37d..02be5b3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,9 +7,14 @@ mod lua;
 mod parser;
 
 use std::env;
+use std::fs::OpenOptions;
+use std::io::BufWriter;
+use std::io::Write;
 use std::process::ExitCode;
 use std::rc::Rc;
+use std::time::UNIX_EPOCH;
 
+use compiler::compiler::CompiledDocument;
 use compiler::compiler::Compiler;
 use compiler::compiler::Target;
 use compiler::navigation::create_navigation;
@@ -18,6 +23,7 @@ use document::document::Document;
 use getopts::Options;
 use parser::langparser::LangParser;
 use parser::parser::Parser;
+use rusqlite::Connection;
 use walkdir::WalkDir;
 
 use crate::parser::source::SourceFile;
@@ -42,38 +48,7 @@ NML version: 0.4\n"
 	);
 }
 
-fn compile(
-	target: Target,
-	doc: &Box<dyn Document>,
-	output: &String,
-	db_path: &Option<String>,
-	navigation: &Navigation,
-	multi_mode: bool,
-) -> bool {
-	let compiler = Compiler::new(target, db_path.clone());
-
-	// Get output from file
-	if multi_mode {
-		let out_file = match doc.get_variable("compiler.output") {
-			None => {
-				eprintln!("Missing required variable `compiler.output` for multifile mode");
-				return false;
-			}
-			Some(var) => output.clone() + "/" + var.to_string().as_str(),
-		};
-
-		let out = compiler.compile(navigation, doc.as_ref());
-		std::fs::write(out_file, out).is_ok()
-	} else {
-		let out = compiler.compile(navigation, doc.as_ref());
-		std::fs::write(output, out).is_ok()
-	}
-}
-
-fn parse(
-	input: &String,
-	debug_opts: &Vec<String>,
-) -> Result<Box<dyn Document<'static>>, String> {
+fn parse(input: &String, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'static>>, String> {
 	println!("Parsing {input}...");
 	let parser = LangParser::default();
 
@@ -108,12 +83,69 @@ fn parse(
 	}
 
 	if parser.has_error() {
-		return Err("Parsing failed aborted due to errors while parsing".to_string())
+		return Err("Parsing failed aborted due to errors while parsing".to_string());
 	}
 
 	Ok(doc)
 }
 
+fn process(
+	target: Target,
+	files: Vec<String>,
+	db_path: &Option<String>,
+	debug_opts: &Vec<String>,
+) -> Result<Vec<CompiledDocument>, String> {
+	let mut compiled = vec![];
+
+	let con = db_path
+		.as_ref()
+		.map_or(Connection::open_in_memory(), |path| Connection::open(path))
+		.map_err(|err| format!("Unable to open connection to the database: {err}"))?;
+	CompiledDocument::init_cache(&con)
+		.map_err(|err| format!("Failed to initialize cached document table: {err}"))?;
+
+	for file in files {
+		let meta = std::fs::metadata(&file)
+			.map_err(|err| format!("Failed to get metadata for `{file}`: {err}"))?;
+
+		let modified = meta
+			.modified()
+			.map_err(|err| format!("Unable to query modification time for `{file}`: {err}"))?;
+
+		let parse_and_compile = || -> Result<CompiledDocument, String> {
+			// Parse
+			let doc = parse(&file, debug_opts)?;
+
+			// Compile
+			let compiler = Compiler::new(target, db_path.clone());
+			let mut compiled = compiler.compile(&*doc);
+
+			// Insert into cache
+			compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs();
+			compiled.insert_cache(&con).map_err(|err| {
+				format!("Failed to insert compiled document from `{file}` into cache: {err}")
+			})?;
+
+			Ok(compiled)
+		};
+
+		let cdoc = match CompiledDocument::from_cache(&con, &file) {
+			Some(compiled) => {
+				if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() {
+					parse_and_compile()?
+				} else {
+					compiled
+				}
+			}
+			None => parse_and_compile()?,
+		};
+
+		compiled.push(cdoc);
+	}
+
+	Ok(compiled)
+}
+
 fn main() -> ExitCode {
 	let args: Vec<String> = env::args().collect();
 	let program = args[0].clone();
@@ -177,8 +209,7 @@ fn main() -> ExitCode {
 	let debug_opts = matches.opt_strs("z");
 	let db_path = matches.opt_str("d");
 
-	let mut docs = vec![];
-
+	let mut files = vec![];
 	if input_meta.is_dir() {
 		if db_path.is_none() {
 			eprintln!("Please specify a database (-d) for directory mode.");
@@ -221,46 +252,53 @@ fn main() -> ExitCode {
 				continue;
 			}
 
-			match parse(&path, &debug_opts)
-			{
-				Ok(doc) => docs.push(doc),
-				Err(e) => {
-					eprintln!("{e}");
-					return ExitCode::FAILURE;
-				}
-			}
+			files.push(path);
 		}
 	} else {
-		match parse(&input, &debug_opts)
-		{
-			Ok(doc) => docs.push(doc),
-			Err(e) => {
-				eprintln!("{e}");
-				return ExitCode::FAILURE;
-			}
-		}
+		files.push(input);
 	}
 
-	// Build navigation
-	let navigation = match create_navigation(&docs)
-	{
-		Ok(nav) => nav,
+	// Parse, compile using the cache
+	let compiled = match process(Target::HTML, files, &db_path, &debug_opts) {
+		Ok(compiled) => compiled,
 		Err(e) => {
 			eprintln!("{e}");
 			return ExitCode::FAILURE;
 		}
 	};
 
-	println!("{navigation:#?}");
-
-	let multi_mode = input_meta.is_dir();
-	for doc in docs
-	{
-		if !compile(Target::HTML, &doc, &output, &db_path, &navigation, multi_mode)
-		{
-			eprintln!("Compilation failed, processing aborted");
+	// Build navigation
+	let navigation = match create_navigation(&compiled) {
+		Ok(nav) => nav,
+		Err(e) => {
+			eprintln!("{e}");
 			return ExitCode::FAILURE;
 		}
+	};
+	let compiled_navigation = navigation.compile(Target::HTML);
+
+	// Output
+	for doc in compiled {
+		let out_path = match doc
+			.get_variable("compiler.output")
+			.or(input_meta.is_file().then_some(&output))
+		{
+			Some(path) => path.clone(),
+			None => {
+				eprintln!("Unable to get output file for `{}`", doc.input);
+				return ExitCode::FAILURE;
+			}
+		};
+
+		let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap();
+		let mut writer = BufWriter::new(file);
+
+		write!(
+			writer,
+			"{}{}{}{}",
+			doc.header, compiled_navigation, doc.body, doc.footer
+		);
+		writer.flush();
 	}
 
 	return ExitCode::SUCCESS;