diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js
index 157e712..e513ea1 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.js
+++ b/ui/src/Pages/AlbumPage/AlbumPage.js
@@ -5,6 +5,8 @@ import AlbumGallery from '../../components/albumGallery/AlbumGallery'
 import PropTypes from 'prop-types'
 import Layout from '../../Layout'
 import useURLParameters from '../../hooks/useURLParameters'
+import useScrollPagination from '../../hooks/useScrollPagination'
+import { Loader } from 'semantic-ui-react'
 
 const albumQuery = gql`
   query albumQuery(
@@ -12,6 +14,8 @@ const albumQuery = gql`
     $onlyFavorites: Boolean
     $mediaOrderBy: String
     $mediaOrderDirection: OrderDirection
+    $limit: Int
+    $offset: Int
   ) {
     album(id: $id) {
       id
@@ -27,6 +31,8 @@ const albumQuery = gql`
       }
       media(
         filter: {
+          limit: $limit
+          offset: $offset
           order_by: $mediaOrderBy
           order_direction: $mediaOrderDirection
         }
@@ -80,15 +86,24 @@ function AlbumPage({ match }) {
     [setParams]
   )
 
-  const { loading, error, data, refetch } = useQuery(albumQuery, {
+  const { loading, error, data, refetch, fetchMore } = useQuery(albumQuery, {
     variables: {
       id: albumId,
       onlyFavorites,
       mediaOrderBy: orderBy,
       mediaOrderDirection: orderDirection,
+      offset: 0,
+      limit: 200,
     },
   })
 
+  const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
+    loading,
+    fetchMore,
+    data,
+    getItems: data => data.album.media,
+  })
+
   const toggleFavorites = useCallback(
     onlyFavorites => {
       if (
@@ -115,6 +130,7 @@ function AlbumPage({ match }) {
   return (
     <Layout title={data ? data.album.title : 'Loading album'}>
       <AlbumGallery
+        ref={containerElem}
         album={data && data.album}
         loading={loading}
         showFavoritesToggle
@@ -125,6 +141,13 @@ function AlbumPage({ match }) {
         setOrdering={setOrdering}
         ordering={{ orderBy, orderDirection }}
       />
+      <Loader
+        style={{ margin: '42px 0 24px 0' }}
+        active={!finishedLoadingMore && !loading}
+        inline="centered"
+      >
+        Loading more media
+      </Loader>
     </Layout>
   )
 }
diff --git a/ui/src/apolloClient.js b/ui/src/apolloClient.js
index f56eb78..db9eca2 100644
--- a/ui/src/apolloClient.js
+++ b/ui/src/apolloClient.js
@@ -114,6 +114,20 @@ const memoryCache = new InMemoryCache({
     SiteInfo: {
       merge: true,
     },
+    MediaURL: {
+      keyFields: ['url'],
+    },
+    Album: {
+      fields: {
+        media: {
+          keyArgs: ['onlyFavorites'],
+          merge(existing = [], incoming) {
+            console.log('merge media', existing, incoming)
+            return [...existing, ...incoming]
+          },
+        },
+      },
+    },
     Query: {
       fields: {
         myTimeline: offsetLimitPagination(['onlyFavorites']),
diff --git a/ui/src/components/albumGallery/AlbumGallery.js b/ui/src/components/albumGallery/AlbumGallery.js
index 4bed335..e4d7b89 100644
--- a/ui/src/components/albumGallery/AlbumGallery.js
+++ b/ui/src/components/albumGallery/AlbumGallery.js
@@ -5,124 +5,129 @@ import PhotoGallery from '../photoGallery/PhotoGallery'
 import AlbumBoxes from './AlbumBoxes'
 import AlbumFilter from '../AlbumFilter'
 
-const AlbumGallery = ({
-  album,
-  loading = false,
-  customAlbumLink,
-  showFilter = false,
-  setOnlyFavorites,
-  setOrdering,
-  ordering,
-  onlyFavorites = false,
-  onFavorite,
-}) => {
-  const [imageState, setImageState] = useState({
-    activeImage: -1,
-    presenting: false,
-  })
+const AlbumGallery = React.forwardRef(
+  (
+    {
+      album,
+      loading = false,
+      customAlbumLink,
+      showFilter = false,
+      setOnlyFavorites,
+      setOrdering,
+      ordering,
+      onlyFavorites = false,
+      onFavorite,
+    },
+    ref
+  ) => {
+    const [imageState, setImageState] = useState({
+      activeImage: -1,
+      presenting: false,
+    })
 
-  const setPresenting = presenting =>
-    setImageState(state => ({ ...state, presenting }))
+    const setPresenting = presenting =>
+      setImageState(state => ({ ...state, presenting }))
 
-  const setPresentingWithHistory = presenting => {
-    setPresenting(presenting)
-    if (presenting) {
-      history.pushState({ imageState }, '')
-    } else {
-      history.back()
-    }
-  }
-
-  const updateHistory = imageState => {
-    history.replaceState({ imageState }, '')
-    return imageState
-  }
-
-  const setActiveImage = activeImage => {
-    setImageState(state => updateHistory({ ...state, activeImage }))
-  }
-
-  const nextImage = () => {
-    setActiveImage((imageState.activeImage + 1) % album.media.length)
-  }
-
-  const previousImage = () => {
-    if (imageState.activeImage <= 0) {
-      setActiveImage(album.media.length - 1)
-    } else {
-      setActiveImage(imageState.activeImage - 1)
-    }
-  }
-
-  useEffect(() => {
-    const updateImageState = event => {
-      setImageState(event.state.imageState)
-    }
-    window.addEventListener('popstate', updateImageState)
-
-    return () => {
-      window.removeEventListener('popstate', updateImageState)
-    }
-  }, [imageState])
-
-  useEffect(() => {
-    setActiveImage(-1)
-  }, [album])
-
-  let subAlbumElement = null
-
-  if (album) {
-    if (album.subAlbums.length > 0) {
-      subAlbumElement = (
-        <AlbumBoxes
-          loading={loading}
-          albums={album.subAlbums}
-          getCustomLink={customAlbumLink}
-        />
-      )
-    }
-  } else {
-    subAlbumElement = <AlbumBoxes loading={loading} />
-  }
-
-  return (
-    <>
-      <AlbumTitle album={album} disableLink />
-      {showFilter && (
-        <AlbumFilter
-          onlyFavorites={onlyFavorites}
-          setOnlyFavorites={setOnlyFavorites}
-          setOrdering={setOrdering}
-          ordering={ordering}
-        />
-      )}
-      {subAlbumElement}
-      {
-        <h2
-          style={{
-            opacity: loading ? 0 : 1,
-            display: album && album.subAlbums.length > 0 ? 'block' : 'none',
-          }}
-        >
-          Images
-        </h2>
+    const setPresentingWithHistory = presenting => {
+      setPresenting(presenting)
+      if (presenting) {
+        history.pushState({ imageState }, '')
+      } else {
+        history.back()
       }
-      <PhotoGallery
-        loading={loading}
-        media={album && album.media}
-        activeIndex={imageState.activeImage}
-        presenting={imageState.presenting}
-        onSelectImage={index => {
-          setActiveImage(index)
-        }}
-        onFavorite={onFavorite}
-        setPresenting={setPresentingWithHistory}
-        nextImage={nextImage}
-        previousImage={previousImage}
-      />
-    </>
-  )
-}
+    }
+
+    const updateHistory = imageState => {
+      history.replaceState({ imageState }, '')
+      return imageState
+    }
+
+    const setActiveImage = activeImage => {
+      setImageState(state => updateHistory({ ...state, activeImage }))
+    }
+
+    const nextImage = () => {
+      setActiveImage((imageState.activeImage + 1) % album.media.length)
+    }
+
+    const previousImage = () => {
+      if (imageState.activeImage <= 0) {
+        setActiveImage(album.media.length - 1)
+      } else {
+        setActiveImage(imageState.activeImage - 1)
+      }
+    }
+
+    useEffect(() => {
+      const updateImageState = event => {
+        setImageState(event.state.imageState)
+      }
+      window.addEventListener('popstate', updateImageState)
+
+      return () => {
+        window.removeEventListener('popstate', updateImageState)
+      }
+    }, [imageState])
+
+    useEffect(() => {
+      setActiveImage(-1)
+    }, [album])
+
+    let subAlbumElement = null
+
+    if (album) {
+      if (album.subAlbums.length > 0) {
+        subAlbumElement = (
+          <AlbumBoxes
+            loading={loading}
+            albums={album.subAlbums}
+            getCustomLink={customAlbumLink}
+          />
+        )
+      }
+    } else {
+      subAlbumElement = <AlbumBoxes loading={loading} />
+    }
+
+    return (
+      <div ref={ref}>
+        <AlbumTitle album={album} disableLink />
+        {showFilter && (
+          <AlbumFilter
+            onlyFavorites={onlyFavorites}
+            setOnlyFavorites={setOnlyFavorites}
+            setOrdering={setOrdering}
+            ordering={ordering}
+          />
+        )}
+        {subAlbumElement}
+        {
+          <h2
+            style={{
+              opacity: loading ? 0 : 1,
+              display: album && album.subAlbums.length > 0 ? 'block' : 'none',
+            }}
+          >
+            Images
+          </h2>
+        }
+        <PhotoGallery
+          loading={loading}
+          media={album && album.media}
+          activeIndex={imageState.activeImage}
+          presenting={imageState.presenting}
+          onSelectImage={index => {
+            setActiveImage(index)
+          }}
+          onFavorite={onFavorite}
+          setPresenting={setPresentingWithHistory}
+          nextImage={nextImage}
+          previousImage={previousImage}
+        />
+      </div>
+    )
+  }
+)
 
 AlbumGallery.propTypes = {
   album: PropTypes.object,
diff --git a/ui/src/components/timelineGallery/TimelineGallery.js b/ui/src/components/timelineGallery/TimelineGallery.js
index 870c6f5..094cfa2 100644
--- a/ui/src/components/timelineGallery/TimelineGallery.js
+++ b/ui/src/components/timelineGallery/TimelineGallery.js
@@ -130,21 +130,16 @@ const TimelineGallery = () => {
       variables: {
         onlyFavorites,
         offset: 0,
-        limit: 10,
+        limit: 50,
       },
     }
   )
 
-  const { containerElem } = useScrollPagination({
+  const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
     loading,
-    fetchMore: () => {
-      console.log('fetching more')
-      fetchMore({
-        variables: {
-          offset: data.myTimeline.length,
-        },
-      })
-    },
+    fetchMore,
+    data,
+    getItems: data => data.myTimeline,
   })
 
   useEffect(() => {
@@ -209,6 +204,13 @@ const TimelineGallery = () => {
         setOnlyFavorites={setOnlyFavorites}
       />
       <GalleryWrapper ref={containerElem}>{timelineGroups}</GalleryWrapper>
+      <Loader
+        style={{ margin: '42px 0 24px 0' }}
+        active={!finishedLoadingMore && !loading}
+        inline="centered"
+      >
+        Loading more media
+      </Loader>
       {presenting && (
         <PresentView
           media={
diff --git a/ui/src/hooks/useScrollPagination.js b/ui/src/hooks/useScrollPagination.js
index 0e03db0..86ae96d 100644
--- a/ui/src/hooks/useScrollPagination.js
+++ b/ui/src/hooks/useScrollPagination.js
@@ -1,8 +1,9 @@
-import { useCallback, useEffect, useRef } from 'react'
+import { useCallback, useEffect, useRef, useState } from 'react'
 
-const useScrollPagination = ({ loading, fetchMore }) => {
+const useScrollPagination = ({ loading, fetchMore, data, getItems }) => {
   const observer = useRef(null)
   const observerElem = useRef(null)
+  const [finished, setFinished] = useState(false)
 
   const reconfigureIntersectionObserver = () => {
     var options = {
@@ -14,18 +15,32 @@ const useScrollPagination = ({ loading, fetchMore }) => {
     // delete old observer
     if (observer.current) observer.current.disconnect()
 
+    if (finished) return
+
     // configure new observer
     observer.current = new IntersectionObserver(entities => {
       console.log('Observing', entities)
       if (entities.find(x => x.isIntersecting == false)) {
-        console.log('load more')
-        fetchMore()
+        let itemCount = getItems(data).length
+        console.log('load more', itemCount)
+        fetchMore({
+          variables: {
+            offset: itemCount,
+          },
+        }).then(result => {
+          const newItemCount = getItems(result.data).length
+          console.log('then', result, itemCount, newItemCount)
+          if (newItemCount == 0) {
+            setFinished(true)
+          }
+        })
       }
     }, options)
 
     // activate new observer
-    if (observerElem.current && !loading)
+    if (observerElem.current && !loading) {
       observer.current.observe(observerElem.current)
+    }
   }
 
   const containerElem = useCallback(node => {
@@ -55,10 +70,11 @@ const useScrollPagination = ({ loading, fetchMore }) => {
   // reconfigure observer if fetchMore function changes
   useEffect(() => {
     reconfigureIntersectionObserver()
-  }, [fetchMore])
+  }, [fetchMore, data, finished])
 
   return {
     containerElem,
+    finished,
   }
 }
 
